@code-yeongyu/senpi 2026.11.6-1 → 2026.11.6-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/bundle/chunks/{chunk-5MWHZH2O.js → chunk-2FDW2LQL.js} +1 -1
- package/dist/bundle/chunks/{chunk-GG2JO3CL.js → chunk-4E7GDG4H.js} +1 -1
- package/dist/bundle/chunks/{chunk-K5JLYACP.js → chunk-4GKU755E.js} +1 -1
- package/dist/bundle/chunks/{chunk-437VLZQZ.js → chunk-7III44PV.js} +1 -1
- package/dist/bundle/chunks/chunk-7PSBD5KS.js +9 -0
- package/dist/bundle/chunks/{chunk-KFWHLVVH.js → chunk-C7OKUC7L.js} +1 -1
- package/dist/bundle/chunks/{chunk-CX32K5KD.js → chunk-CF5OGBC4.js} +7 -7
- package/dist/bundle/chunks/{chunk-7TGZJH6Z.js → chunk-D5MRJWUK.js} +40 -39
- package/dist/bundle/chunks/{chunk-E6L5VZSM.js → chunk-DSWDJ7HT.js} +1 -1
- package/dist/bundle/chunks/{chunk-6NMTFURP.js → chunk-ECW7VBMB.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CSLJLM6.js → chunk-GKAYPJ4E.js} +2 -2
- package/dist/bundle/chunks/{chunk-KCGUUIUS.js → chunk-GNSMNSL7.js} +1 -1
- package/dist/bundle/chunks/{chunk-V3JSQ3PG.js → chunk-GSKCWXXG.js} +1 -1
- package/dist/bundle/chunks/{chunk-USKWGPA6.js → chunk-GTJJ73DE.js} +1 -1
- package/dist/bundle/chunks/{chunk-HHSYTDKO.js → chunk-HLM46XEB.js} +2 -2
- package/dist/bundle/chunks/{chunk-A4F4J2KH.js → chunk-I3PHVJAS.js} +2 -2
- package/dist/bundle/chunks/{chunk-LAMRXFHZ.js → chunk-LNPAT22L.js} +1 -1
- package/dist/bundle/chunks/chunk-NH7SYIGN.js +2 -0
- package/dist/bundle/chunks/{chunk-ZIIDIVTG.js → chunk-R2Z7CX77.js} +1 -1
- package/dist/bundle/chunks/{chunk-32I42CEY.js → chunk-RQ6CHXQ2.js} +1 -1
- package/dist/bundle/chunks/chunk-SJI6UB23.js +2 -0
- package/dist/bundle/chunks/{chunk-KUIEHAPR.js → chunk-T2SMQPRE.js} +1 -1
- package/dist/bundle/chunks/{chunk-IPG434JT.js → chunk-TIPKAMSS.js} +1 -1
- package/dist/bundle/chunks/{chunk-RI5ABX7A.js → chunk-TMXRR3N7.js} +2 -2
- package/dist/bundle/chunks/chunk-VD7AKTGP.js +3 -0
- package/dist/bundle/chunks/{chunk-22JICYKS.js → chunk-WAOGBBZR.js} +1 -1
- package/dist/bundle/chunks/{chunk-6MHCFJDB.js → chunk-WGRKM3N6.js} +2 -2
- package/dist/bundle/chunks/{chunk-WF5R2VSP.js → chunk-X5ZVYNOL.js} +1 -1
- package/dist/bundle/chunks/{chunk-H2FBGZNP.js → chunk-XCMII2VQ.js} +2 -2
- package/dist/bundle/chunks/{chunk-5JUCTKYS.js → chunk-YK3ZZCHI.js} +1 -1
- package/dist/bundle/chunks/{cli-main-ZQBSX5H5.js → cli-main-T74YJGIF.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-7U7354GG.js → hand-entry-AYRUGE2A.js} +2 -2
- package/dist/bundle/chunks/{headless-host-7LKTG76U.js → headless-host-ES74ZB6M.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-NLSWH2CA.js → help-fast-path-TMTGLOBG.js} +1 -1
- package/dist/bundle/chunks/{host-command-3YRFN35E.js → host-command-YRXZE4TV.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-6XLVIECJ.js → host-lifecycle-6VCXMPDW.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/{host-session-command-F7USXUZV.js → host-session-command-N7IN7ZDV.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-4RF3PUQ2.js → interactive-mode-B5SQDLH7.js} +1 -1
- package/dist/bundle/chunks/{list-tips-H6VGDFTS.js → list-tips-DU57HHKA.js} +1 -1
- package/dist/bundle/chunks/{main-G2SRZYQN.js → main-UQFVFCNY.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-RPN54GUC.js → multi-session-host-XSUJDHFZ.js} +5 -5
- package/dist/bundle/chunks/{openai-codex-responses-MJZKUS6I.js → openai-codex-responses-7V6LTDRU.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-D2VFD463.js → package-manager-cli-GLUNPQJU.js} +1 -1
- package/dist/bundle/chunks/{role-37WVG42T.js → role-RRFFHZF5.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-QELPMM4N.js → rotation-stream-BN2YK2MC.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-P4D3DVYD.js → rpc-mode-JYN3E7P7.js} +1 -1
- package/dist/bundle/chunks/rpc-monitor-source-C6VQ3BC4.js +3 -0
- package/dist/bundle/chunks/{run-attached-tui-CGA6O5FR.js → run-attached-tui-W236SEOR.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XWSNOAGG.js → schedule-command-B2EJTBOE.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-YIMLHGLG.js +3 -0
- package/dist/bundle/chunks/{session-picker-OA2AUVZG.js → session-picker-E6ZONUJB.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +294 -292
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.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/cli/project-trust-headless.d.ts +13 -0
- package/dist/cli/project-trust-headless.js +35 -0
- package/dist/cli/project-trust.js +13 -50
- package/dist/cli/schedule-delivery.d.ts +4 -0
- package/dist/cli/schedule-delivery.js +6 -0
- package/dist/cli/schedule-runner.js +8 -0
- package/dist/cli/session-opening.d.ts +19 -0
- package/dist/cli/session-opening.js +47 -1
- package/dist/core/agent-session-runtime.d.ts +12 -0
- package/dist/core/agent-session-runtime.js +41 -0
- package/dist/core/agent-session.js +13 -22
- package/dist/core/egress/api-table.d.ts +121 -0
- package/dist/core/egress/api-table.js +41 -0
- package/dist/core/egress/classify.d.ts +19 -0
- package/dist/core/egress/classify.js +197 -0
- package/dist/core/egress/credential-fields.d.ts +18 -0
- package/dist/core/egress/credential-fields.js +83 -0
- package/dist/core/egress/custody-sidecar.d.ts +44 -0
- package/dist/core/egress/custody-sidecar.js +91 -0
- package/dist/core/egress/device-custody.d.ts +12 -0
- package/dist/core/egress/device-custody.js +82 -0
- package/dist/core/egress/endpoint.d.ts +33 -0
- package/dist/core/egress/endpoint.js +115 -0
- package/dist/core/execute-tool-on-host.d.ts +26 -0
- package/dist/core/execute-tool-on-host.js +96 -0
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js +15 -0
- package/dist/core/extensions/builtin/goal/index.js +15 -3
- package/dist/core/extensions/builtin/goal/stop-pause.d.ts +2 -0
- package/dist/core/extensions/builtin/goal/stop-pause.js +5 -0
- package/dist/core/extensions/builtin/herdr/herdr-state.d.ts +10 -0
- package/dist/core/extensions/builtin/herdr/herdr-state.js +34 -6
- package/dist/core/extensions/builtin/herdr/index.js +6 -2
- package/dist/core/extensions/builtin/mcp/service.d.ts +9 -1
- package/dist/core/extensions/builtin/mcp/service.js +32 -1
- package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.d.ts +43 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.js +158 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-render.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-render.js +4 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-source.d.ts +5 -2
- package/dist/core/extensions/builtin/terminal/rpc-monitor-source.js +19 -4
- package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.d.ts +4 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.js +15 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.d.ts +10 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.js +28 -2
- package/dist/core/extensions/builtin/terminal/rpc-remote-source.d.ts +59 -0
- package/dist/core/extensions/builtin/terminal/rpc-remote-source.js +15 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +14 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor-rpc.js +8 -1
- package/dist/core/extensions/loader.js +8 -0
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/foreign-session-holders.d.ts +1 -1
- package/dist/core/foreign-session-holders.js +3 -3
- package/dist/core/session-import/jsonl-import.d.ts +8 -12
- package/dist/core/session-import/jsonl-import.js +99 -40
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.js +7 -2
- package/dist/core/session-store/durable-binding.d.ts +2 -0
- package/dist/core/session-store/durable-binding.js +26 -4
- package/dist/core/settings-manager.d.ts +20 -0
- package/dist/core/settings-manager.js +51 -0
- package/dist/core/trust-manager.d.ts +1 -18
- package/dist/core/trust-manager.js +3 -124
- package/dist/core/trust-requiring-resources.d.ts +21 -0
- package/dist/core/trust-requiring-resources.js +189 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/main.js +42 -12
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/session-control-commands.js +2 -0
- package/dist/modes/rpc/connection-handler.js +17 -0
- package/dist/modes/rpc/connection-question-bridge.js +7 -4
- package/dist/modes/rpc/effect-commands.d.ts +24 -0
- package/dist/modes/rpc/host-boot-extensions.d.ts +4 -0
- package/dist/modes/rpc/host-boot-extensions.js +17 -0
- package/dist/modes/rpc/host-lifecycle-shutdown.js +11 -2
- package/dist/modes/rpc/loopless-runtime-settings.js +5 -1
- package/dist/modes/rpc/method-classes-schema-session.d.ts +33 -0
- package/dist/modes/rpc/method-classes-schema-session.js +4 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +33 -0
- package/dist/modes/rpc/method-classes.d.ts +4 -0
- package/dist/modes/rpc/method-classes.js +7 -0
- package/dist/modes/rpc/multi-session-host.d.ts +2 -0
- package/dist/modes/rpc/multi-session-host.js +7 -2
- package/dist/modes/rpc/project-workspace-commands.d.ts +60 -0
- package/dist/modes/rpc/project-workspace-commands.js +145 -0
- package/dist/modes/rpc/rpc-error-types.d.ts +11 -1
- package/dist/modes/rpc/rpc-error-types.js +10 -0
- package/dist/modes/rpc/rpc-input-validation.js +4 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +13 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +13 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +15 -0
- package/dist/modes/rpc/session-attachment-gate.js +2 -0
- package/dist/modes/rpc/session-command-activity.js +2 -0
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +41 -2
- package/docs/extensions.md +17 -1
- package/docs/rpc.md +121 -4
- package/docs/terminal-tools.md +8 -0
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-FUQJK2TU.js +0 -2
- package/dist/bundle/chunks/chunk-POZMRM6V.js +0 -2
- package/dist/bundle/chunks/chunk-XBG63MJZ.js +0 -8
- package/dist/bundle/chunks/chunk-YP47G4YP.js +0 -3
- package/dist/bundle/chunks/rpc-monitor-source-KKN2UP4T.js +0 -3
- package/dist/bundle/chunks/session-control-endpoint-OST7WAKP.js +0 -3
package/docs/rpc.md
CHANGED
|
@@ -2744,6 +2744,15 @@ context - inside a provider scope of their own, then drops them.
|
|
|
2744
2744
|
- A host whose sessions run in worker isolates (`--session-runtime worker`) has nothing it can warm from the host
|
|
2745
2745
|
process: it answers `state: "unsupported"` and does not advertise the capability. A draining generation refuses with
|
|
2746
2746
|
`host_draining`.
|
|
2747
|
+
- A host whose launch carried a private descriptor table (`SENPI_PRIVATE_FDS`) runs this warm itself once at boot,
|
|
2748
|
+
for its default profile (the host's cwd and context, no `kind`), right after it starts listening, so an extension
|
|
2749
|
+
can claim its private channel before any client connects. A `warm` of that profile then joins it, or answers
|
|
2750
|
+
`already_warm` once it is done, and never loads again; a boot load that fails is logged to stderr and the host
|
|
2751
|
+
keeps serving. Without a table nothing loads before an `open_session` or `warm`.
|
|
2752
|
+
The boot warm's factory call is what claims the channel: its services are then dropped and its provider scope is
|
|
2753
|
+
closed, so every later session's `takeInheritedPrivateChannel` returns `undefined`. An extension must keep its
|
|
2754
|
+
channel handling host-global, never tied to that factory call's `pi`. This applies only to in-process hosts: a
|
|
2755
|
+
`--session-runtime worker` host has no `warm`, so one handed a table loads nothing at boot.
|
|
2747
2756
|
|
|
2748
2757
|
Probe `warm` in `get_protocol_info` capabilities before sending it: an older host answers an unknown command with an
|
|
2749
2758
|
error.
|
|
@@ -3382,8 +3391,9 @@ registrant opens no socket and writes no registry directory.
|
|
|
3382
3391
|
user's draft still holds it.
|
|
3383
3392
|
- `get_protocol_info`: `mode: "tui"`, `endpoint_kind: "tui"`, `capabilities: ["tui_control", "session_status",
|
|
3384
3393
|
"tui_observer", "observe_reads", "entries_tail"]` (`tui_observer`: the observe-and-drive set below), `generation: 0`, the endpoint's
|
|
3385
|
-
`instanceId`,
|
|
3386
|
-
controls below lists no
|
|
3394
|
+
`instanceId`, `commands`: every command this endpoint accepts, and `method_classes`: the same table and digest a
|
|
3395
|
+
host's answer carries (one builder serves both). A terminal from before the session controls below lists no
|
|
3396
|
+
`commands`; one from before senpi#6158 carries no `method_classes`.
|
|
3387
3397
|
- `list_sessions`: exactly one row, `kind: "interactive"`, `surface: "tui"`, `attachments: 1`.
|
|
3388
3398
|
- `get_state`: the RPC session state plus `turn_epoch`, `blocking_question`, `compacting`,
|
|
3389
3399
|
`editor_has_draft` and `state_version`.
|
|
@@ -3958,8 +3968,8 @@ On a socket host, session-scoped commands require the current connection to be a
|
|
|
3958
3968
|
in the following effect-based read allow-list:
|
|
3959
3969
|
|
|
3960
3970
|
`get_state`, `get_messages`, `get_entries`, `get_tree`, `get_fork_messages`,
|
|
3961
|
-
`get_last_assistant_text`, `get_media`, `get_commands`, `get_loaded_surfaces`,
|
|
3962
|
-
`get_available_models`, `get_available_thinking_levels`, `get_fast_mode`, `get_session_stats`,
|
|
3971
|
+
`get_last_assistant_text`, `get_media`, `get_commands`, `get_loaded_surfaces`, `get_project_trust`,
|
|
3972
|
+
`get_project_settings`, `get_available_models`, `get_available_thinking_levels`, `get_fast_mode`, `get_session_stats`,
|
|
3963
3973
|
`get_pending_inputs`, `get_steering_messages`, `get_follow_up_messages`, `get_auth_providers`,
|
|
3964
3974
|
`get_provider_accounts`, `resolve_path`, `memory_report`, `eval_cells.list`, `eval_cells.peek`,
|
|
3965
3975
|
and `check_reload_veto`.
|
|
@@ -6031,6 +6041,113 @@ Extension rows come directly from the session's loaded resource inventory, not f
|
|
|
6031
6041
|
|
|
6032
6042
|
In multi-session mode this is a session-scoped command and requires the routing `sessionId`.
|
|
6033
6043
|
|
|
6044
|
+
### Workspace trust and project settings
|
|
6045
|
+
|
|
6046
|
+
A project folder's settings file, skills, prompt templates, extensions, themes and MCP servers load only when the
|
|
6047
|
+
project is trusted. The terminal asks when a session opens; an RPC session is never asked, so it stays untrusted
|
|
6048
|
+
until a client saves a decision with `set_project_trust`. These four commands act on the routed session's working
|
|
6049
|
+
directory (in multi-session mode each requires the routing `sessionId`) and use the same storage and rules as
|
|
6050
|
+
the terminal: the device's `trust.json` and the project's own settings file.
|
|
6051
|
+
|
|
6052
|
+
#### get_project_trust
|
|
6053
|
+
|
|
6054
|
+
```json
|
|
6055
|
+
{"type": "get_project_trust"}
|
|
6056
|
+
```
|
|
6057
|
+
|
|
6058
|
+
```json
|
|
6059
|
+
{
|
|
6060
|
+
"type": "response",
|
|
6061
|
+
"command": "get_project_trust",
|
|
6062
|
+
"success": true,
|
|
6063
|
+
"data": {
|
|
6064
|
+
"cwd": "/home/user/project",
|
|
6065
|
+
"projectTrusted": false,
|
|
6066
|
+
"requiresTrust": true,
|
|
6067
|
+
"saved": null,
|
|
6068
|
+
"locked": false,
|
|
6069
|
+
"choices": [
|
|
6070
|
+
{ "choice": "trust", "label": "Trust", "trusted": true, "path": "/home/user/project" },
|
|
6071
|
+
{ "choice": "trust_parent", "label": "Trust parent folder (/home/user)", "trusted": true, "path": "/home/user" },
|
|
6072
|
+
{ "choice": "do_not_trust", "label": "Do not trust", "trusted": false, "path": "/home/user/project" }
|
|
6073
|
+
]
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
```
|
|
6077
|
+
|
|
6078
|
+
- `projectTrusted`: whether this session runs with the project trusted now. (`get_state.projectTrusted` reports
|
|
6079
|
+
only a decision saved for the folder itself.)
|
|
6080
|
+
- `requiresTrust`: whether the folder has anything trust gates; a folder without such files counts as trusted.
|
|
6081
|
+
- `saved`: the nearest decision saved at or above the folder, or `null`.
|
|
6082
|
+
- `locked`: the engine was launched with `--approve` or `--no-approve`; that flag outranks every saved decision,
|
|
6083
|
+
so `set_project_trust` is refused.
|
|
6084
|
+
- `choices`: exactly the choices the terminal's `/trust` command offers.
|
|
6085
|
+
|
|
6086
|
+
#### set_project_trust
|
|
6087
|
+
|
|
6088
|
+
Saves one of the `/trust` choices and applies it to the session at once: project resources load (or unload)
|
|
6089
|
+
as they would on `reload`, and the next session opened in the folder gets the same decision. It is never
|
|
6090
|
+
implied by `open_session` or by a permission preset; only this explicit request saves a decision.
|
|
6091
|
+
|
|
6092
|
+
```json
|
|
6093
|
+
{"type": "set_project_trust", "choice": "trust"}
|
|
6094
|
+
```
|
|
6095
|
+
|
|
6096
|
+
`choice` is `trust` (this folder), `trust_parent` (the parent folder; clears this folder's own entry, as `/trust`
|
|
6097
|
+
does) or `do_not_trust`. The response carries the same `data` as `get_project_trust`, read after the change.
|
|
6098
|
+
Errors: `invalid_argument` for an unknown choice, `project_trust_locked` when the launch named the trust, and
|
|
6099
|
+
`project_trust_extension_decided` when a `project_trust` handler of an extension loaded before trust (never a
|
|
6100
|
+
project extension) decides the other way: such a handler decides before any saved choice when a session opens, so
|
|
6101
|
+
the request is refused and nothing is saved or applied. A folder with nothing trust gates stays trusted whatever is
|
|
6102
|
+
chosen.
|
|
6103
|
+
|
|
6104
|
+
#### get_project_settings
|
|
6105
|
+
|
|
6106
|
+
Reads the project settings file as stored. Reading runs nothing, so it works while the project is untrusted.
|
|
6107
|
+
|
|
6108
|
+
```json
|
|
6109
|
+
{"type": "get_project_settings"}
|
|
6110
|
+
```
|
|
6111
|
+
|
|
6112
|
+
```json
|
|
6113
|
+
{
|
|
6114
|
+
"type": "response",
|
|
6115
|
+
"command": "get_project_settings",
|
|
6116
|
+
"success": true,
|
|
6117
|
+
"data": {
|
|
6118
|
+
"cwd": "/home/user/project",
|
|
6119
|
+
"path": "/home/user/project/.senpi/settings.json",
|
|
6120
|
+
"exists": true,
|
|
6121
|
+
"projectTrusted": true,
|
|
6122
|
+
"settings": { "defaultThinkingLevel": "high" },
|
|
6123
|
+
"error": null
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
```
|
|
6127
|
+
|
|
6128
|
+
`settings` is `null` when the file does not exist or does not parse; `error` then names the parse failure.
|
|
6129
|
+
|
|
6130
|
+
#### set_project_setting
|
|
6131
|
+
|
|
6132
|
+
Writes one top-level key of the project settings file through the same locked, merge-on-write path the
|
|
6133
|
+
terminal's project setters use; `"value": null` removes the key. The value is stored as given, like an edit to
|
|
6134
|
+
the file: values the session reads when it starts (default model and thinking level, steering and follow-up
|
|
6135
|
+
modes) apply after `reload` or in the next session.
|
|
6136
|
+
|
|
6137
|
+
```json
|
|
6138
|
+
{"type": "set_project_setting", "key": "defaultThinkingLevel", "value": "high"}
|
|
6139
|
+
```
|
|
6140
|
+
|
|
6141
|
+
The response carries the same `data` as `get_project_settings`, read after the write. Errors:
|
|
6142
|
+
`invalid_argument` when `key` is not an identifier (`[A-Za-z][A-Za-z0-9_]*`) or `value` is missing,
|
|
6143
|
+
`project_not_trusted` while the project is untrusted (trust it first), `project_settings_unreadable` when the
|
|
6144
|
+
existing file does not parse (nothing is written), and `project_settings_write_failed` when writing the file fails
|
|
6145
|
+
(permissions, a held lock).
|
|
6146
|
+
|
|
6147
|
+
`get_project_trust` is an `observe` read; `set_project_trust`, `get_project_settings` and `set_project_setting`
|
|
6148
|
+
are `device_admin` (a saved trust decision decides whether project code runs on the device, and the settings
|
|
6149
|
+
commands read and write a file in the session's folder).
|
|
6150
|
+
|
|
6034
6151
|
### extension_request
|
|
6035
6152
|
|
|
6036
6153
|
Invoke a request handler registered by an extension through `pi.rpc.handle(name, handler)`:
|
package/docs/terminal-tools.md
CHANGED
|
@@ -165,6 +165,14 @@ with streaming collapsed to `rpc <label> streaming +<n> deltas (last: "...")` pe
|
|
|
165
165
|
`attachments` count does not change. `persistent: true` survives a restart and reports what moved while
|
|
166
166
|
detached (`changed while detached`, `closed (gone while detached)`, `host ... restarted (generation a -> b)`).
|
|
167
167
|
|
|
168
|
+
A session on another device is `<device>/<session>` (or `<device>/*` for every session there) when an
|
|
169
|
+
extension registered a remote source with `pi.registerRpcRemoteSource`; without one such a target is
|
|
170
|
+
refused. Its lines read exactly like local ones. A lost device is said once
|
|
171
|
+
(`rpc device <device> unreachable (<reason>); re-watching`), the resync after the gap reports each change
|
|
172
|
+
once, each settled turn the resync names is reported once (`rpc <label> turn <id> settled while detached`)
|
|
173
|
+
while a live turn end is rendered as it arrives, and an
|
|
174
|
+
access that ended for good settles the watch (`rpc <device>/<session> access ended (<reason>)`).
|
|
175
|
+
|
|
168
176
|
### Anti-patterns
|
|
169
177
|
|
|
170
178
|
| Anti-pattern | Do instead |
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.11.6-
|
|
3
|
+
"version": "2026.11.6-2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@earendil-works/chord": "0.99.1",
|
|
95
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.6-
|
|
96
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.6-
|
|
97
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.6-
|
|
95
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.6-2",
|
|
96
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.6-2",
|
|
97
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.6-2",
|
|
98
98
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
99
99
|
"ajv": "8.20.0",
|
|
100
100
|
"chalk": "6.0.0",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"yaml": "2.9.1",
|
|
116
116
|
"@anthropic-ai/claude-agent-sdk": "0.3.306",
|
|
117
117
|
"@anthropic-ai/sdk": "0.127.0",
|
|
118
|
-
"@code-yeongyu/senpi-codemode": "2026.11.6-
|
|
119
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.6-
|
|
118
|
+
"@code-yeongyu/senpi-codemode": "2026.11.6-2",
|
|
119
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.6-2",
|
|
120
120
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
121
121
|
"@mozilla/readability": "0.6.0",
|
|
122
122
|
"linkedom": "0.18.13",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{external_exports}from"./chunk-KUPQO7MC.js";var clientMessageIdentitySchema=external_exports.object({clientMessageId:external_exports.string().min(1).max(256).optional(),clientTurnId:external_exports.string().min(1).max(256).optional()});function clientMessageIdentity(value={}){return{...value.clientMessageId===void 0?{}:{clientMessageId:value.clientMessageId},...value.clientTurnId===void 0?{}:{clientTurnId:value.clientTurnId}}}function readClientMessageIdentity(value){let parsed=clientMessageIdentitySchema.safeParse(value);return parsed.success?clientMessageIdentity(parsed.data):{}}var RPC_ERROR_UNKNOWN_SESSION="unknown_session",RPC_ERROR_SESSION_NOT_ATTACHED="session_not_attached";var RPC_ERROR_SESSION_CLOSING="session_closing";var RPC_ERROR_SESSION_HELD="session_held";var RPC_ERROR_MISSING_SESSION_ID="missing_session_id";var RPC_ERROR_INVALID_PATH="invalid_path",RPC_ERROR_INVALID_ARGUMENT="invalid_argument",RPC_ERROR_RESPONSE_TOO_LARGE="response_too_large",RPC_ERROR_RESPONSE_UNWRITABLE="response_unwritable",RPC_ERROR_OPEN_FAILED="open_failed",RPC_ERROR_OWNER_CHANGED="owner_changed";var RPC_ERROR_MEDIA_NOT_FOUND="media_not_found",RPC_ERROR_INVALID_SESSION_CONTEXT="invalid_session_context",RPC_ERROR_INVALID_SESSION_ENV="invalid_session_env",RPC_ERROR_INVALID_SUBSCRIPTION="invalid_subscription",RPC_ERROR_UNKNOWN_SUBSCRIPTION="unknown_subscription",RPC_ERROR_SUBSCRIPTION_LIMIT="subscription_limit",RPC_ERROR_UNSUPPORTED_TRANSPORT="unsupported_transport",RPC_ERROR_FEATURE_DISABLED="feature_disabled",RPC_ERROR_INVALID_SESSION_KIND="invalid_session_kind",RPC_ERROR_INVALID_LAUNCH_PROFILE="invalid_launch_profile";var RPC_ERROR_WARM_FAILED="warm_failed";var RPC_ERROR_ENTRY_NOT_FOUND="not_found",RPC_ERROR_MODEL_NOT_FOUND="model_not_found",RPC_ERROR_EFFECT_ID_CONFLICT="effect_id_conflict",RPC_ERROR_TRANSCRIPT_UNREADABLE="transcript_unreadable",RPC_ERROR_PROVIDER_UNAVAILABLE="provider_unavailable",RPC_ERROR_PROVIDER_CREDENTIALS_MISSING="provider_credentials_missing",RPC_ERROR_PROVIDER_CREDENTIALS_EXPIRED="provider_credentials_expired",RPC_ERROR_NO_MODEL_SELECTED="no_model_selected",RPC_ERROR_TURN_CONFLICT="turn_conflict",RPC_ERROR_CONTEXT_BUDGET="context_budget",RPC_ERROR_EVAL_CELL_NOT_FOUND="eval_cell_not_found",RPC_ERROR_INVALID_EVAL_CELL_REQUEST="invalid_eval_cell_request",RPC_ERROR_EVAL_CELLS_UNAVAILABLE="eval_cells_unavailable";var RPC_ERROR_UNKNOWN_COMMAND="unknown_command",RPC_ERROR_TURN_ACTIVE="turn_active",RPC_ERROR_SESSION_BUSY="session_busy",RPC_ERROR_ATTACHED="attached",RPC_ERROR_INVALID_RELEASE_REASON="invalid_release_reason",RPC_ERROR_RELEASE_UNSUPPORTED="release_unsupported",RPC_ERROR_RELEASE_FAILED="release_failed",RPC_ERROR_HOST_DRAINING="host_draining";var STEER_WHEN_IDLE_CAPABILITY="steer_when_idle",USER_ACTIVITY_CAPABILITY="user_activity",SESSION_MODEL_SCOPE_CAPABILITY="session_model_scope";export{clientMessageIdentitySchema,clientMessageIdentity,readClientMessageIdentity,RPC_ERROR_UNKNOWN_SESSION,RPC_ERROR_SESSION_NOT_ATTACHED,RPC_ERROR_SESSION_CLOSING,RPC_ERROR_SESSION_HELD,RPC_ERROR_MISSING_SESSION_ID,RPC_ERROR_INVALID_PATH,RPC_ERROR_INVALID_ARGUMENT,RPC_ERROR_RESPONSE_TOO_LARGE,RPC_ERROR_RESPONSE_UNWRITABLE,RPC_ERROR_OPEN_FAILED,RPC_ERROR_OWNER_CHANGED,RPC_ERROR_MEDIA_NOT_FOUND,RPC_ERROR_INVALID_SESSION_CONTEXT,RPC_ERROR_INVALID_SESSION_ENV,RPC_ERROR_INVALID_SUBSCRIPTION,RPC_ERROR_UNKNOWN_SUBSCRIPTION,RPC_ERROR_SUBSCRIPTION_LIMIT,RPC_ERROR_UNSUPPORTED_TRANSPORT,RPC_ERROR_FEATURE_DISABLED,RPC_ERROR_INVALID_SESSION_KIND,RPC_ERROR_INVALID_LAUNCH_PROFILE,RPC_ERROR_WARM_FAILED,RPC_ERROR_ENTRY_NOT_FOUND,RPC_ERROR_MODEL_NOT_FOUND,RPC_ERROR_EFFECT_ID_CONFLICT,RPC_ERROR_TRANSCRIPT_UNREADABLE,RPC_ERROR_PROVIDER_UNAVAILABLE,RPC_ERROR_PROVIDER_CREDENTIALS_MISSING,RPC_ERROR_PROVIDER_CREDENTIALS_EXPIRED,RPC_ERROR_NO_MODEL_SELECTED,RPC_ERROR_TURN_CONFLICT,RPC_ERROR_CONTEXT_BUDGET,RPC_ERROR_EVAL_CELL_NOT_FOUND,RPC_ERROR_INVALID_EVAL_CELL_REQUEST,RPC_ERROR_EVAL_CELLS_UNAVAILABLE,RPC_ERROR_UNKNOWN_COMMAND,RPC_ERROR_TURN_ACTIVE,RPC_ERROR_SESSION_BUSY,RPC_ERROR_ATTACHED,RPC_ERROR_INVALID_RELEASE_REASON,RPC_ERROR_RELEASE_UNSUPPORTED,RPC_ERROR_RELEASE_FAILED,RPC_ERROR_HOST_DRAINING,STEER_WHEN_IDLE_CAPABILITY,USER_ACTIVITY_CAPABILITY,SESSION_MODEL_SCOPE_CAPABILITY};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{listHostEndpoints}from"./chunk-Y7Z7XPUF.js";import{randomBytes}from"node:crypto";var MONITOR_ID_ALPHABET="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function allocateMonitorId(){let id="mon_",buffer=0,bits=0;for(let byte of randomBytes(10))for(buffer=buffer<<8|byte,bits+=8;bits>=5;)bits-=5,id+=MONITOR_ID_ALPHABET[buffer>>bits&31];return id}var STATUS_KINDS=new Set(["session_status","effect_status"]);function parseRpcTarget(target,selfDurableId){let trimmed=target.trim();return trimmed===""?{error:'target is empty; pass "children", "all" or a session id, path or st_ task id'}:trimmed==="children"?selfDurableId===void 0?{error:'target "children" needs a session with a durable id; save the session first, or watch "all"'}:{kind:"children",parent:selfDurableId}:trimmed==="all"?{kind:"all"}:trimmed.startsWith("st_")?{kind:"task",taskId:trimmed}:{kind:"id",id:trimmed}}function defaultEvents(target){return target.kind==="children"?["session_status","effect_status"]:["session_status"]}function subscriptionFor(target,events){let sessions=target.kind==="children"?{childrenOf:target.parent}:target.kind==="id"?{ids:[target.id]}:"all",content=events==="all"?"all":events.filter(kind=>!STATUS_KINDS.has(kind)),mirror=content==="all"?"all":content.length===0?void 0:content;return{sessions,include_workers:!0,include_context:target.kind==="children"||target.kind==="task",effects:events==="all"||events.includes("effect_status"),...mirror===void 0?{}:{mirror}}}function wantsRecord(events,exclude,type){return exclude.includes(type)?!1:events==="all"||events.includes(type)}function matchesTask(target,row){return target.kind!=="task"?!0:row.context?.task_id===target.taskId}async function endpointSockets(agentDir){return(await listHostEndpoints(agentDir)).map(endpoint=>endpoint.socket).filter(socket=>socket!==null)}export{allocateMonitorId,parseRpcTarget,defaultEvents,subscriptionFor,wantsRecord,matchesTask,endpointSockets};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{resolveMovedPath}from"./chunk-USKWGPA6.js";import{advanceSessionFence}from"./chunk-SOMFLLPD.js";import{killWindowsProcessTree}from"./chunk-E6L5VZSM.js";import{APP_NAME}from"./chunk-SODMQEHO.js";import{ownProcessStartedAtMs,processBootAtMs,sameBoot}from"./chunk-QN2BHP3M.js";import{processStartTimeMs,sameProcessStartMs}from"./chunk-O5RYI3IW.js";import{randomUUID}from"node:crypto";import{mkdir,readdir,readFile,rename,rm,stat,writeFile}from"node:fs/promises";import{dirname,join}from"node:path";var InvalidScheduledJobError=class extends Error{constructor(message){super(message),this.name="InvalidScheduledJobError"}};function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function requireString(raw,key){let value=raw[key];if(typeof value!="string"||value.length===0)throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a non-empty string`);return value}function requireCount(raw,key){let value=raw[key];if(typeof value!="number"||!Number.isSafeInteger(value)||value<0)throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a non-negative integer`);return value}function nullableCount(raw,key){return raw[key]===null||raw[key]===void 0?null:requireCount(raw,key)}function nullableString(raw,key){let value=raw[key];if(value==null)return null;if(typeof value!="string")throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a string`);return value}function parseScheduledJob(raw){if(!isRecord(raw))throw new InvalidScheduledJobError("scheduled job must be a JSON object");if(raw.version!==1)throw new InvalidScheduledJobError(`unsupported scheduled job version: ${JSON.stringify(raw.version)}`);let id=requireString(raw,"id");if(!/^sch_[a-z0-9]{12}$/.test(id))throw new InvalidScheduledJobError(`invalid scheduled job id: ${id}`);let everyMs=nullableCount(raw,"everyMs");if(everyMs!==null&&everyMs<60*1e3)throw new InvalidScheduledJobError("scheduled job recurrence must be at least 60s");if(everyMs!==null&&everyMs>316224e5)throw new InvalidScheduledJobError("scheduled job recurrence must be at most 366 days");let prompt=requireString(raw,"prompt");if(Buffer.byteLength(prompt,"utf8")>16384)throw new InvalidScheduledJobError("scheduled job prompt exceeds 16384 bytes");let createdAt=requireCount(raw,"createdAt"),dueAt=requireCount(raw,"dueAt");return{version:1,id,sessionId:requireString(raw,"sessionId"),sessionFile:nullableString(raw,"sessionFile"),cwd:requireString(raw,"cwd"),prompt,createdAt,dueAt,everyMs,fireCount:requireCount(raw,"fireCount"),lastFiredAt:nullableCount(raw,"lastFiredAt"),lastError:nullableString(raw,"lastError")}}function nextRecurringDueAt(dueAt,everyMs,now){if(dueAt>now)return dueAt;let missed=Math.floor((now-dueAt)/everyMs)+1;return dueAt+missed*everyMs}function formatScheduledMessage(job,firedAt){let recurrence=job.everyMs===null?"":`, repeats every ${Math.round(job.everyMs/1e3)}s`;return`[Scheduled prompt ${job.id}: created ${new Date(job.createdAt).toISOString()}, due ${new Date(job.dueAt).toISOString()}, fired ${new Date(firedAt).toISOString()}${recurrence}]
|
|
3
|
-
${job.prompt}`}var STATE_DIRS=["pending","firing","failed"],JOB_ID=/^sch_[a-z0-9]{12}$/,RECORD_FILE=/^(sch_[a-z0-9]{12})(?:@(\d+)(?:~(\d+)-(\d+))?)?\.json$/;function scheduleDir(agentDir){return join(agentDir,"schedule")}function isScheduledJobId(id){return JOB_ID.test(id)}function isFileSystemError(error,code){return error instanceof Error&&"code"in error&&error.code===code}async function ensureDir(dir){await mkdir(dir,{recursive:!0,mode:448})}async function writeAtomic(filePath,contents){await ensureDir(dirname(filePath));let tempPath=join(dirname(filePath),`.schedule-${randomUUID()}.tmp`);try{await writeFile(tempPath,contents,{encoding:"utf8",mode:384}),await rename(tempPath,filePath)}catch(error){throw await rm(tempPath,{force:!0}),error}}function serialize(job){return`${JSON.stringify(job,null,2)}
|
|
4
|
-
`}async function readJobFile(path,expectedId){if((await stat(path)).size>65536)throw new InvalidScheduledJobError(`scheduled job file exceeds ${65536} bytes`);let parsed;try{parsed=JSON.parse(await readFile(path,"utf8"))}catch(error){throw isFileSystemError(error,"ENOENT")?error:new InvalidScheduledJobError("scheduled job file is not valid JSON")}let job=parseScheduledJob(parsed);if(job.id!==expectedId)throw new InvalidScheduledJobError(`scheduled job id ${job.id} does not match its file`);return job}async function listScheduledJobs(dir){let jobs=[],invalid=[];for(let state of STATE_DIRS){let names;try{names=await readdir(join(dir,state))}catch(error){if(isFileSystemError(error,"ENOENT"))continue;throw error}for(let name of names.sort()){let match=RECORD_FILE.exec(name);if(match===null)continue;let[,id,occurrence,pid,startedAt]=match,file=join(state,name);if(!(state==="pending"?occurrence===void 0:state==="firing"?pid!==void 0:occurrence!==void 0&&pid===void 0)||id===void 0){invalid.push({state,file,error:`unexpected record name for ${state}/`});continue}try{let job=await readJobFile(join(dir,file),id);jobs.push({state,job,file,...occurrence===void 0?{}:{occurrence:Number(occurrence)},...pid===void 0||startedAt===void 0?{}:{owner:{pid:Number(pid),processStartedAtMs:Number(startedAt)}}})}catch(error){if(isFileSystemError(error,"ENOENT"))continue;invalid.push({state,file,error:error instanceof Error?error.message:String(error)})}}}return jobs.sort((a,b)=>a.job.dueAt-b.job.dueAt),{jobs,invalid}}async function createScheduledJob(dir,input,now){let job=parseScheduledJob({version:1,id:`sch_${randomUUID().replace(/-/g,"").slice(0,12)}`,...input,createdAt:now,fireCount:0,lastFiredAt:null});return await writeAtomic(join(dir,"pending",`${job.id}.json`),serialize(job)),job}function tombstonePath(dir,id){return join(dir,"cancelled",id)}async function isCancelled(dir,id){try{return await stat(tombstonePath(dir,id)),!0}catch(error){if(isFileSystemError(error,"ENOENT"))return!1;throw error}}function runnersDir(dir){return join(dir,"runners")}import{randomUUID as randomUUID2}from"node:crypto";import{link,readdir as readdir2,rename as rename2,rm as rm2,stat as stat2,writeFile as writeFile2}from"node:fs/promises";import{join as join2}from"node:path";var TOMBSTONE_RETENTION_MS=1440*60*1e3;async function cancelScheduledJob(dir,id,options={}){if(!isScheduledJobId(id))return;let records=(await listScheduledJobs(dir)).jobs.filter(record=>record.job.id===id),representative=records.find(record=>record.state==="pending")??records[0];if(representative===void 0||options.sessionId!==void 0&&representative.job.sessionId!==options.sessionId)return;await writeAtomic(tombstonePath(dir,id),`${new Date().toISOString()}
|
|
5
|
-
`);let inFlight=(await occurrenceNumbers(dir,"firing",id)).length>0;await rm2(join2(dir,"pending",`${id}.json`),{force:!0});let failed=records.filter(record=>record.state==="failed");for(let record of failed)await rm2(join2(dir,record.file),{force:!0});return{job:representative.job,inFlight,removedFailed:failed.length}}function occurrenceFile(id,occurrence,owner){return join2("firing",`${id}@${occurrence}~${owner.pid}-${owner.processStartedAtMs}.json`)}async function restorePending(dir,record,id){let pending=join2(dir,"pending",`${id}.json`);try{await link(join2(dir,record),pending)}catch(error){if(!isFileSystemError(error,"EEXIST"))throw error}await rm2(join2(dir,record),{force:!0}),await isCancelled(dir,id)&&await rm2(pending,{force:!0})}async function claimOccurrence(dir,job,owner){let record=occurrenceFile(job.id,job.fireCount+1,owner);await ensureDir(join2(dir,"firing"));try{await rename2(join2(dir,"pending",`${job.id}.json`),join2(dir,record))}catch(error){if(isFileSystemError(error,"ENOENT"))return;throw error}if(await isCancelled(dir,job.id)){await rm2(join2(dir,record),{force:!0});return}if((await readJobFile(join2(dir,record),job.id)).fireCount!==job.fireCount){await restorePending(dir,record,job.id);return}return record}async function rearmRecurringJob(dir,next){if(await isCancelled(dir,next.id))return;let path=join2(dir,"pending",`${next.id}.json`);await writeAtomic(path,serialize(next)),await isCancelled(dir,next.id)&&await rm2(path,{force:!0})}async function occurrenceNumbers(dir,state,id){let names;try{names=await readdir2(join2(dir,state))}catch(error){if(isFileSystemError(error,"ENOENT"))return[];throw error}let prefix=`${id}@`;return names.filter(name=>name.startsWith(prefix)).map(name=>Number.parseInt(name.slice(prefix.length),10)).filter(occurrence=>Number.isSafeInteger(occurrence))}async function restoreAbandonedSchedule(dir,job,occurrence,now){if(job.everyMs===null||await isCancelled(dir,job.id))return;let pending=join2(dir,"pending",`${job.id}.json`);if(await stat2(pending).then(()=>!0,()=>!1)||[...await occurrenceNumbers(dir,"firing",job.id),...await occurrenceNumbers(dir,"failed",job.id)].some(n=>n>occurrence))return;let next={...job,fireCount:occurrence,dueAt:nextRecurringDueAt(job.dueAt,job.everyMs,now),lastError:null};await ensureDir(join2(dir,"pending"));let temp=join2(dir,"pending",`.schedule-${randomUUID2()}.tmp`);try{await writeFile2(temp,serialize(next),{encoding:"utf8",mode:384}),await link(temp,pending)}catch(error){if(!isFileSystemError(error,"EEXIST"))throw error;return}finally{await rm2(temp,{force:!0})}if(await isCancelled(dir,job.id)){await rm2(pending,{force:!0});return}return next}async function settleOccurrence(dir,record,job,occurrence,outcome){if(!outcome.ok&&!await isCancelled(dir,job.id)){let failed=join2(dir,"failed",`${job.id}@${occurrence}.json`);await writeAtomic(failed,serialize({...job,lastError:outcome.error})),await isCancelled(dir,job.id)&&await rm2(failed,{force:!0});let older=(await occurrenceNumbers(dir,"failed",job.id)).sort((a,b)=>b-a);for(let stale of older.slice(10))await rm2(join2(dir,"failed",`${job.id}@${stale}.json`),{force:!0})}await rm2(join2(dir,record),{force:!0})}async function pruneTombstones(dir,listing,now){let names;try{names=await readdir2(join2(dir,"cancelled"))}catch(error){if(isFileSystemError(error,"ENOENT"))return;throw error}let live=new Set(listing.jobs.filter(record=>record.state!=="failed").map(record=>record.job.id));for(let id of names){if(!isScheduledJobId(id)||live.has(id))continue;let info=await stat2(tombstonePath(dir,id)).catch(()=>{});info!==void 0&&now-info.mtimeMs>TOMBSTONE_RETENTION_MS&&await rm2(tombstonePath(dir,id),{force:!0})}}import{mkdir as mkdir2,readdir as readdir3,stat as stat4}from"node:fs/promises";import{join as join3}from"node:path";import{randomUUID as randomUUID3}from"node:crypto";import{link as link2,open,readFile as readFile3,rename as rename3,stat as stat3,unlink}from"node:fs/promises";import{execFile}from"node:child_process";import{readFile as readFile2}from"node:fs/promises";var LINUX_CLOCK_TICKS_PER_SECOND=100;async function linuxProcessStartMs(pid){let[stat5,systemStat]=await Promise.all([readFile2(`/proc/${pid}/stat`,"utf8"),readFile2("/proc/stat","utf8")]),afterComm=stat5.slice(stat5.lastIndexOf(")")+2).split(" "),startTicks=Number(afterComm[19]),bootSeconds=Number(/^btime (\d+)/m.exec(systemStat)?.[1]);if(!(!Number.isFinite(startTicks)||!Number.isFinite(bootSeconds)))return Math.round((bootSeconds+startTicks/LINUX_CLOCK_TICKS_PER_SECOND)*1e3)}function execText(command,args,env){return new Promise((resolve,reject)=>{execFile(command,[...args],{encoding:"utf8",timeout:5e3,windowsHide:!0,...env===void 0?{}:{env}},(error,stdout)=>{error?reject(error):resolve(stdout)})})}async function darwinProcessStartMs(pid){let text=(await execText("ps",["-o","lstart=","-p",String(pid)],{...process.env,LC_ALL:"C",LANG:"C"})).trim();if(text.length!==0)return processStartTimeMs(text)}async function windowsProcessStartMs(pid){let script=`(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().ToString('o')`,text=(await execText("powershell",["-NoProfile","-NonInteractive","-Command",script])).trim();return processStartTimeMs(text)}async function readProcessStartMs(pid){try{switch(process.platform){case"linux":return await linuxProcessStartMs(pid);case"darwin":return await darwinProcessStartMs(pid);case"win32":return await windowsProcessStartMs(pid);default:return}}catch{return}}function errorCode(error){if(typeof error=="object"&&error!==null&&"code"in error&&typeof error.code=="string")return error.code}async function unlinkIfPresent(path){try{await unlink(path)}catch(error){if(errorCode(error)!=="ENOENT")throw error}}async function readLeaseText(path){try{return await readFile3(path,"utf8")}catch(error){if(errorCode(error)==="ENOENT")return;throw error}}async function writeTemp(path,content,mode){let temp=`${path}.${process.pid}.${randomUUID3().slice(0,8)}.tmp`,file=await open(temp,"wx",mode);try{await file.writeFile(content,"utf8")}finally{await file.close()}return temp}async function publishExclusive(path,content,options={}){let temp=await writeTemp(path,content,options.mode);try{await link2(temp,path)}catch(error){let code=errorCode(error);if(code!=="ENOTSUP"&&code!=="EOPNOTSUPP"&&code!=="EPERM")throw error;let file=await open(path,"wx",options.mode);try{await file.writeFile(content,"utf8")}finally{await file.close()}}finally{await unlinkIfPresent(temp)}}async function publishReplace(path,content,options={}){let temp=await writeTemp(path,content,options.mode);try{await rename3(temp,path)}catch(error){throw await unlinkIfPresent(temp),error}}function pidAlive(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}var UNREADABLE_LOCK_STALE_MS=3e4;function readLockHolder(raw){try{let parsed=JSON.parse(raw);if(typeof parsed!="object"||parsed===null||!("pid"in parsed)||!("bootAtMs"in parsed)||!("processStartedAtMs"in parsed))return;let{pid,bootAtMs,processStartedAtMs}=parsed;return typeof pid!="number"||typeof bootAtMs!="number"||typeof processStartedAtMs!="number"?void 0:{pid,bootAtMs,processStartedAtMs}}catch{return}}async function reclaimLockState(lock,probes={}){let raw=await readLeaseText(lock);if(raw===void 0)return{state:"free"};let holder=readLockHolder(raw);if(holder===void 0)return await stat3(lock).then(info=>Date.now()-info.mtimeMs,()=>0)>UNREADABLE_LOCK_STALE_MS?{state:"stale",raw}:{state:"held"};if(!sameBoot(holder.bootAtMs,processBootAtMs()))return{state:"stale",raw};if(holder.pid===process.pid)return sameProcessStartMs(holder.processStartedAtMs,ownProcessStartedAtMs())?{state:"held",holder}:{state:"stale",raw};if(!(probes.isProcessAlive??pidAlive)(holder.pid))return{state:"stale",raw};let observed=await(probes.readProcessStartMs??readProcessStartMs)(holder.pid).catch(()=>{});return observed===void 0?{state:"held",holder,unproven:!0}:sameProcessStartMs(holder.processStartedAtMs,observed)?{state:"held",holder}:{state:"stale",raw}}async function breakStaleLock(lock,raw){let aside=`${lock}.${process.pid}.${randomUUID3().slice(0,8)}.broken`;try{await rename3(lock,aside)}catch(error){if(errorCode(error)==="ENOENT")return;throw error}let moved=await readFile3(aside,"utf8");if(moved!==raw)try{await publishExclusive(lock,moved)}catch(error){if(errorCode(error)!=="EEXIST")throw error}await unlinkIfPresent(aside)}async function takeReclaimLock(lock,probes){let mine=JSON.stringify({pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs()});for(let attempt=0;attempt<3;attempt+=1){try{return await publishExclusive(lock,mine),mine}catch(error){if(errorCode(error)!=="EEXIST")throw error}let seen=await reclaimLockState(lock,probes);if(seen.state==="held")return{holder:seen.holder};seen.state==="stale"&&await breakStaleLock(lock,seen.raw)}return{}}async function reclaimInspected(path,inspected,probes={}){let lock=`${path}.lock`,taken=await takeReclaimLock(lock,probes);if(typeof taken!="string")return{outcome:"busy",...taken.holder?{holder:taken.holder}:{}};try{return await readLeaseText(path)!==inspected?{outcome:"changed"}:(await unlinkIfPresent(path),{outcome:"removed"})}finally{await readLeaseText(lock)===taken&&await unlinkIfPresent(lock)}}var RUNNER_HEARTBEAT_STALE_MS=12e4,RUNNER_HEARTBEAT_INTERVAL_MS=3e4,DEFAULT_MAX_DELIVERY_MS=9e5,PRIVATE={mode:384};function ownRunnerIdentity(){return{pid:process.pid,processStartedAtMs:ownProcessStartedAtMs()}}function leasePath(dir,pid){return join3(runnersDir(dir),`${pid}.json`)}async function writeRunnerLease(dir,lease,now){await mkdir2(runnersDir(dir),{recursive:!0,mode:448});let identity=ownRunnerIdentity();await publishReplace(leasePath(dir,identity.pid),JSON.stringify({...identity,bootAtMs:processBootAtMs(),beatAt:now,...lease}),PRIVATE)}async function removeRunnerLease(dir){await unlinkIfPresent(leasePath(dir,process.pid))}function parseLease(raw){try{let parsed=JSON.parse(raw);if(typeof parsed!="object"||parsed===null)return;let beatAt="beatAt"in parsed?parsed.beatAt:void 0;if(typeof beatAt!="number")return;let watch="watch"in parsed&&parsed.watch===!0,exec="exec"in parsed&&typeof parsed.exec=="string"?parsed.exec:null;return{beatAt,watch,exec}}catch{return}}async function liveRunners(dir){let names;try{names=await readdir3(runnersDir(dir))}catch(error){if(error instanceof Error&&"code"in error&&error.code==="ENOENT")return[];throw error}let runners=[];for(let name of names){if(!/^\d+\.json$/.test(name))continue;let path=join3(runnersDir(dir),name),state=await reclaimLockState(path);if(state.state==="stale"&&await breakStaleLock(path,state.raw),state.state!=="held"||state.holder===void 0)continue;let raw=await readLeaseText(path),lease=raw===void 0?void 0:parseLease(raw);lease!==void 0&&runners.push({pid:state.holder.pid,processStartedAtMs:state.holder.processStartedAtMs,...lease})}return runners}async function isWatchRunnerAvailable(dir,now){return(await liveRunners(dir)).some(runner=>runner.watch&&now-runner.beatAt<=RUNNER_HEARTBEAT_STALE_MS)}function isOwnerAmong(owner,runners){return runners.some(runner=>runner.pid===owner.pid&&sameProcessStartMs(runner.processStartedAtMs,owner.processStartedAtMs))}function sessionLockPath(dir,sessionId){return join3(dir,"sessions",`${encodeURIComponent(sessionId)}.lock`)}function isDeliveryGroupAlive(pid){try{return process.kill(process.platform==="win32"?pid:-pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}function isPidAlive(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}async function orphanedDelivery(raw,lockAgeMs,probes){let parsed;try{parsed=JSON.parse(raw)}catch{return}if(typeof parsed!="object"||parsed===null)return;let field=key=>key in parsed?Reflect.get(parsed,key):void 0,boot=field("bootAtMs");if(typeof boot!="number"||!sameBoot(boot,processBootAtMs()))return;let pid=field("deliveryPid"),startedAt=field("deliveryStartedAtMs");if(typeof pid!="number"||typeof startedAt!="number"){let maxDeliveryMs=field("maxDeliveryMs"),runner=field("pid");return field("gated")===!1&&typeof maxDeliveryMs=="number"&&lockAgeMs<=maxDeliveryMs&&typeof runner=="number"?runner:void 0}if(!isDeliveryGroupAlive(pid))return;if(!isPidAlive(pid))return pid;let leaderStart=await(probes.readProcessStartMs??readProcessStartMs)(pid).catch(()=>{});return leaderStart!==void 0&&sameProcessStartMs(startedAt,leaderStart)?pid:void 0}async function acquireSessionDeliveryLock(dir,sessionId,options={}){let probes=options,path=sessionLockPath(dir,sessionId),identity={pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs(),gated:options.gated??process.platform!=="win32",maxDeliveryMs:options.maxDeliveryMs??DEFAULT_MAX_DELIVERY_MS},mine=JSON.stringify(identity);for(let attempt=0;attempt<3;attempt+=1){try{await publishExclusive(path,mine,PRIVATE);let tail=Promise.resolve(),serialized=operation=>{let run=tail.then(operation);return tail=run.catch(()=>{}),run};return{acquired:!0,attachDelivery:pid=>serialized(async()=>{let startedAt=Math.floor(Date.now()/1e3)*1e3;if(await readLeaseText(path)!==mine)throw new Error("session delivery lock was lost");let next=JSON.stringify({...identity,deliveryPid:pid,deliveryStartedAtMs:startedAt});await publishReplace(path,next,PRIVATE),mine=next}),release:()=>serialized(async()=>{await readLeaseText(path)===mine&&await unlinkIfPresent(path)})}}catch(error){let code=errorCode(error);if(code==="ENOENT"){await mkdir2(join3(dir,"sessions"),{recursive:!0,mode:448});continue}if(code!=="EEXIST")throw error}let state=await reclaimLockState(path);if(state.state==="held")return{acquired:!1,heldByPid:state.holder?.pid};if(state.state==="stale"){let lockAgeMs=await stat4(path).then(info=>Date.now()-info.mtimeMs,()=>0),delivering=await orphanedDelivery(state.raw,lockAgeMs,probes);if(delivering!==void 0)return{acquired:!1,heldByPid:delivering};await breakStaleLock(path,state.raw)}}return{acquired:!1,heldByPid:void 0}}import{randomUUID as randomUUID4}from"node:crypto";import{mkdir as mkdir3,readFile as readFile4,rename as rename4,rm as rm3,writeFile as writeFile3}from"node:fs/promises";import{dirname as dirname2,join as join4}from"node:path";var InvalidSidecarStoreError=class extends Error{constructor(message,options){super(message,options),this.name="InvalidSidecarStoreError"}},UnsupportedSidecarStoreVersionError=class extends Error{constructor(message){super(message),this.name="UnsupportedSidecarStoreVersionError"}},mutationTails=new Map,snapshots=new Map;function encodedSessionId(sessionId){return encodeURIComponent(sessionId)}function sidecarFilePath(ref){return join4(ref.baseDir,`${encodedSessionId(ref.sessionId)}.json`)}function createSidecarStore(options){let ref={baseDir:options.baseDir,sessionId:options.sessionId},filePath=sidecarFilePath(ref);async function read(){let raw;try{raw=await readFile4(filePath,"utf8")}catch(error){if(isFileSystemError2(error,"ENOENT"))return null;throw error}let state=parsePayload(raw,options,ref);return snapshots.set(filePath,state),state}async function write(state){await mkdir3(dirname2(filePath),{recursive:!0}),await writeAtomic2(filePath,`${JSON.stringify(state,null,2)}
|
|
6
|
-
`,options.tempPrefix),snapshots.set(filePath,state)}function mutate(fn){return serializeByKey(filePath,async()=>{let next=await fn(await read());return await write(next),next})}return{filePath,read,write,mutate,snapshot:()=>snapshots.get(filePath),clear:()=>{snapshots.delete(filePath)}}}async function writeAtomic2(filePath,contents,tempPrefix){let tempPath=join4(dirname2(filePath),`.${tempPrefix}-${randomUUID4()}.tmp`);try{await writeFile3(tempPath,contents,{encoding:"utf8",mode:384}),await rename4(tempPath,filePath)}catch(error){try{await rm3(tempPath,{force:!0})}catch(cleanupError){throw new AggregateError([error,cleanupError],"sidecar store write failed and its temporary file could not be removed")}throw error}}function parsePayload(raw,options,ref){let parsed;try{parsed=JSON.parse(raw)}catch(error){throw new InvalidSidecarStoreError("sidecar store contains unparseable JSON",{cause:error})}if(!isRecord2(parsed))throw new InvalidSidecarStoreError("sidecar store must be a JSON object");if(parsed.version!==options.version)throw new UnsupportedSidecarStoreVersionError(`unsupported sidecar store version: ${JSON.stringify(parsed.version)} (expected ${options.version})`);if(typeof parsed.sessionId!="string"||parsed.sessionId.length===0)throw new InvalidSidecarStoreError("sidecar store is missing a sessionId");if(parsed.sessionId!==ref.sessionId)throw new InvalidSidecarStoreError(`sidecar store sessionId ${parsed.sessionId} does not match the session it was loaded for`);return options.parse(parsed,ref)}function serializeByKey(key,operation){let run=(mutationTails.get(key)??Promise.resolve()).then(operation),tail=run.then(()=>{},()=>{});return mutationTails.set(key,tail),tail.then(()=>{mutationTails.get(key)===tail&&mutationTails.delete(key)}),run}function isRecord2(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function isFileSystemError2(error,code){return error instanceof Error&&"code"in error&&error.code===code}import{randomUUID as randomUUID5}from"node:crypto";import{existsSync,mkdirSync,readFileSync,renameSync,rmdirSync,statSync,unlinkSync,writeFileSync}from"node:fs";import{mkdir as mkdir4}from"node:fs/promises";import{dirname as dirname3,join as join6}from"node:path";var SessionHeldError=class extends Error{constructor(sessionFile,holders){let who=holders.map(h=>h.cwd?`pid ${h.pid} in ${h.cwd}`:`pid ${h.pid}`).join("; ");super(`This session is open in another ${APP_NAME} process (${who}). Quit it there, then try again.`),this.code="ESESSIONHELD",this.name="SessionHeldError",this.sessionFile=sessionFile,this.holders=holders}},SessionMovingError=class extends Error{constructor(sessionFile,moverPid){let who=moverPid===void 0?"another process":`another process (pid ${moverPid})`;super(`Session ${sessionFile} is being moved by ${who}. Try again in a moment.`),this.code="ESESSIONMOVING",this.name="SessionMovingError",this.moverPid=moverPid}},SessionMovedError=class extends Error{constructor(sessionFile){super(`Session ${sessionFile} no longer exists: another process moved it. Resume it from its new location.`),this.code="ESESSIONMOVED",this.name="SessionMovedError"}};import{readdir as readdir4}from"node:fs/promises";import{join as join5}from"node:path";var HOLDER_RECORD=/^\d+\.json$/;function parseCwd(raw){let parsed=JSON.parse(raw);if(!(typeof parsed!="object"||parsed===null||!("cwd"in parsed)))return typeof parsed.cwd=="string"?parsed.cwd:void 0}async function fenced(fenceSession){return fenceSession===void 0?!1:fenceSession().then(()=>!0,()=>!1)}function holderGeneration(parsed){return typeof parsed!="object"||parsed===null?{}:{..."host_instance"in parsed&&typeof parsed.host_instance=="string"?{host_instance:parsed.host_instance}:{},..."host_daemon_dir"in parsed&&typeof parsed.host_daemon_dir=="string"?{host_daemon_dir:parsed.host_daemon_dir}:{}}}async function liveHolders(dir,probes={},fenceSession){let holders=[];for(let name of await readdir4(dir)){if(!HOLDER_RECORD.test(name))continue;let path=join5(dir,name),before=await readLeaseText(path),state=await reclaimLockState(path,probes);if(state.state==="stale"&&await breakStaleLock(path,state.raw),state.state!=="held"||state.holder===void 0)continue;let raw=await readLeaseText(path),stable=raw!==void 0&&raw===before,parsed=stable?JSON.parse(raw):void 0;if(stable&&state.unproven===!0&&await probes.holderEnded?.(holderGeneration(parsed))===!0&&await fenced(fenceSession)){await breakStaleLock(path,raw);continue}let endpoint=typeof parsed=="object"&&parsed!==null&&"host_instance"in parsed&&typeof parsed.host_instance=="string"?{host_instance:parsed.host_instance,..."host_socket"in parsed&&typeof parsed.host_socket=="string"?{host_socket:parsed.host_socket}:{}}:{};holders.push({pid:state.holder.pid,cwd:stable?parseCwd(raw):void 0,...endpoint,...probes.markUnproven===!0&&state.unproven===!0?{unproven:!0}:{}})}return holders}var HOLDERS_DIR="session-holders",MOVE_LOCK="move.lock",MOVE_LOCK_STALE_MS=3e4,DEFAULT_MOVE_LOCK_WAIT_MS=5e3,MOVE_LOCK_RETRY_MS=100;function holdersDir(sessionFile,sessionId){return join6(dirname3(resolveMovedPath(sessionFile)),HOLDERS_DIR,encodedSessionId(sessionId))}function identityRecord(cwd,endpoint){return JSON.stringify({pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs(),...cwd===void 0?{}:{cwd},...endpoint})}function unlinkIfPresentSync(path){try{unlinkSync(path)}catch(error){if(errorCode(error)!=="ENOENT")throw error}}function removeDirIfEmpty(dir){try{rmdirSync(dir)}catch(error){let code=errorCode(error);if(code!=="ENOENT"&&code!=="ENOTEMPTY"&&code!=="EEXIST")throw error}}var holderRecordRefCounts=new Map,exitCleanupInstalled=!1;function installExitCleanup(){exitCleanupInstalled||(exitCleanupInstalled=!0,process.once("exit",()=>{for(let record of holderRecordRefCounts.keys())unlinkIfPresentSync(record)}))}function pidAlive2(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}function activeMover(lock){let raw,ageMs;try{raw=readFileSync(lock,"utf8"),ageMs=Date.now()-statSync(lock).mtimeMs}catch(error){if(errorCode(error)==="ENOENT")return;throw error}if(ageMs>MOVE_LOCK_STALE_MS)return;let pid=parsePid(raw);return pid===void 0?{pid}:pid===process.pid||pidAlive2(pid)?{pid}:void 0}function parsePid(raw){try{let parsed=JSON.parse(raw);return typeof parsed!="object"||parsed===null||!("pid"in parsed)?void 0:typeof parsed.pid=="number"?parsed.pid:void 0}catch(error){if(error instanceof SyntaxError)return;throw error}}function holdSessionFile(sessionFile,sessionId,options){let dir=holdersDir(sessionFile,sessionId),record=join6(dir,`${process.pid}.json`),shared=holderRecordRefCounts.get(record)??0;shared===0&&(publishHolderRecord(dir,record,identityRecord(options.cwd,options.holderEndpoint)),installExitCleanup()),holderRecordRefCounts.set(record,shared+1);let released=!1,release=()=>{if(released)return;released=!0;let left=(holderRecordRefCounts.get(record)??1)-1;if(left>0){holderRecordRefCounts.set(record,left);return}holderRecordRefCounts.delete(record),unlinkIfPresentSync(record),removeDirIfEmpty(dir)},mover=activeMover(join6(dir,MOVE_LOCK));if(mover!==void 0)throw release(),new SessionMovingError(sessionFile,mover.pid);if(options.expectExisting&&!existsSync(resolveMovedPath(sessionFile)))throw release(),new SessionMovedError(sessionFile);return{release}}function publishHolderRecord(dir,record,content){for(let attempt=0;;attempt+=1){let temp=`${record}.${randomUUID5().slice(0,8)}.tmp`;try{mkdirSync(dir,{recursive:!0}),writeFileSync(temp,content),renameSync(temp,record);return}catch(error){if(errorCode(error)!=="ENOENT"||attempt>0)throw error}}}async function liveSessionHolders(sessionFile,sessionId,probes={}){try{return await liveHolders(holdersDir(sessionFile,sessionId),probes,()=>advanceSessionFence(sessionFile))}catch(error){if(errorCode(error)==="ENOENT")return[];throw error}}async function createLockDir(dir,tolerateEnoent){try{return await mkdir4(dir,{recursive:!0}),!1}catch(error){if(!tolerateEnoent||errorCode(error)!=="ENOENT")throw error;return!0}}async function takeMoveLock(sessionFile,lock,waitMs){let mine=identityRecord(),deadline=Date.now()+waitMs,lockDirVanished=!1;for(;;){try{return await publishExclusive(lock,mine),mine}catch(error){let code=errorCode(error);if(code==="ENOENT"){lockDirVanished=await createLockDir(dirname3(lock),!lockDirVanished);continue}if(code!=="EEXIST")throw error}let state=await reclaimLockState(lock);if(state.state==="stale"){await breakStaleLock(lock,state.raw);continue}if(state.state!=="free"){if(Date.now()>=deadline)throw new SessionMovingError(sessionFile,state.holder?.pid);await new Promise(resolve=>setTimeout(resolve,MOVE_LOCK_RETRY_MS))}}}async function withSessionMoveLock(sessionFile,sessionId,move,options={}){let dir=holdersDir(sessionFile,sessionId),lock=join6(dir,MOVE_LOCK),mine=await takeMoveLock(sessionFile,lock,options.waitMs??DEFAULT_MOVE_LOCK_WAIT_MS);try{let holders=await liveHolders(dir);if(holders.length>0)throw new SessionHeldError(sessionFile,holders);return await move()}finally{await readLeaseText(lock)===mine&&await unlinkIfPresent(lock),removeDirIfEmpty(dir)}}import{spawn}from"node:child_process";import{existsSync as existsSync2}from"node:fs";function movedJobPaths(job){return{sessionFile:job.sessionFile===null?null:resolveMovedPath(job.sessionFile),cwd:resolveMovedPath(job.cwd)}}async function deferWhileSessionOpen(job){if(job.sessionFile===null||!existsSync2(job.sessionFile))return;let holders=await liveSessionHolders(job.sessionFile,job.sessionId);if(holders.length!==0)return`session is open in pid ${holders.map(holder=>holder.pid).join(", ")}; waiting until it closes`}var STDERR_TAIL_CHARS=2e3,POSIX_GATE='IFS= read -r _ <&3 || exit 75; exec 3<&-; exec "$0" "$@"';function killTree(pid){if(process.platform==="win32"){killWindowsProcessTree(pid);return}try{process.kill(-pid,"SIGKILL")}catch{}}function runDeliveryProcess(launch,timeoutMs,context={}){return new Promise(resolve=>{let posix=process.platform!=="win32",child=posix?spawn("/bin/sh",["-c",POSIX_GATE,launch.command,...launch.args],{cwd:launch.cwd,env:launch.env,stdio:["pipe","ignore","pipe","pipe"],detached:!0}):spawn(launch.command,[...launch.args],{cwd:launch.cwd,env:launch.env,stdio:["pipe","ignore","pipe"],windowsHide:!0,windowsVerbatimArguments:launch.windowsVerbatimArguments===!0}),stderr="",failure,timer=setTimeout(()=>{failure??=`delivery timed out after ${Math.round(timeoutMs/1e3)}s`,child.pid!==void 0&&killTree(child.pid)},timeoutMs);child.stderr?.on("data",chunk=>{stderr=(stderr+chunk.toString("utf8")).slice(-STDERR_TAIL_CHARS)}),child.stdin?.on("error",()=>{});let gate=posix?child.stdio[3]:null;gate?.on("error",()=>{}),child.on("error",error=>{failure??=error.message}),child.on("close",(code,signal)=>{if(clearTimeout(timer),failure!==void 0)resolve({ok:!1,error:failure});else if(code===0)resolve({ok:!0});else{let status=code===null?`signal ${signal}`:`exit code ${code}`;resolve({ok:!1,error:stderr.trim()?`${status}: ${stderr.trim()}`:status})}});let pid=child.pid;pid!==void 0&&Promise.resolve(context.onSpawn?.(pid)).then(()=>{gate!=null&&"end"in gate&&gate.end(`go
|
|
7
|
-
`),child.stdin?.end(launch.stdin??"")},error=>{failure??=`could not register the delivery: ${error instanceof Error?error.message:String(error)}`,killTree(pid)})})}function eventEnv(event){return{...process.env,SENPI_SCHEDULE_ID:event.id,SENPI_SCHEDULE_SESSION_ID:event.sessionId,SENPI_SCHEDULE_SESSION_FILE:event.sessionFile??"",SENPI_SCHEDULE_CWD:event.cwd}}function execHookDelivery(command,timeoutMs){let shell=process.platform==="win32"?{command:process.env.ComSpec??"cmd.exe",args:["/d","/s","/c",`"${command}"`],windowsVerbatimArguments:!0}:{command:"/bin/sh",args:["-c",command]};return(event,context)=>runDeliveryProcess({...shell,env:eventEnv(event),stdin:`${JSON.stringify(event)}
|
|
8
|
-
`,cwd:process.cwd()},timeoutMs,context)}function sessionResumeDelivery(senpi,timeoutMs){return async(event,context)=>existsSync2(event.cwd)?event.sessionFile!==null&&!existsSync2(event.sessionFile)?{ok:!1,error:`session file no longer exists: ${event.sessionFile}`}:runDeliveryProcess({command:senpi.command,args:[...senpi.args,"-p","--session",event.sessionFile??event.sessionId,event.message],cwd:event.cwd,env:eventEnv(event)},timeoutMs,context):{ok:!1,error:`working directory no longer exists: ${event.cwd}`}}import{join as join7}from"node:path";var DEFERRED_RETRY_MS=15e3,ABANDONED_OCCURRENCE_ERROR="the runner exited while delivering this occurrence; outcome unknown, not retried";async function recoverAbandoned(options,records){let firing=records.filter(record=>record.state==="firing");if(firing.length===0)return{events:[],restoredDueAt:[]};let runners=await(options.runners??(()=>liveRunners(options.dir)))(),events=[],restoredDueAt=[];for(let record of firing){if(record.owner===void 0||record.occurrence===void 0||record.owner.pid===options.owner.pid&&record.owner.processStartedAtMs===options.owner.processStartedAtMs||isOwnerAmong(record.owner,runners))continue;let restored=await restoreAbandonedSchedule(options.dir,record.job,record.occurrence,options.now());restored!==void 0&&restoredDueAt.push(restored.dueAt),await settleOccurrence(options.dir,record.file,record.job,record.occurrence,{ok:!1,error:ABANDONED_OCCURRENCE_ERROR}),events.push({event:"abandoned",id:record.job.id,sessionId:record.job.sessionId,occurrence:record.occurrence,error:ABANDONED_OCCURRENCE_ERROR})}return{events,restoredDueAt}}async function freshPendingJob(options,id){let job=await readJobFile(join7(options.dir,"pending",`${id}.json`),id).catch(error=>{if(!(error instanceof Error&&"code"in error&&error.code==="ENOENT"))throw error});if(!(job===void 0||job.dueAt>options.now()||await isCancelled(options.dir,id)))return job}async function fireOne(options,listed){let lock=await acquireSessionDeliveryLock(options.dir,listed.sessionId,{maxDeliveryMs:options.deliveryTimeoutMs});if(!lock.acquired){let holder=lock.heldByPid===void 0?"another runner":`process ${lock.heldByPid}`;return{event:"deferred",id:listed.id,sessionId:listed.sessionId,reason:`${holder} is delivering to this session`}}try{let job=await freshPendingJob(options,listed.id);if(job===void 0)return;let deferReason=await options.shouldDefer?.({...job,...movedJobPaths(job)});return deferReason!==void 0?{event:"deferred",id:job.id,sessionId:job.sessionId,reason:deferReason}:await deliverClaimed(options,job,pid=>lock.attachDelivery(pid))}finally{await lock.release()}}async function deliverClaimed(options,job,onSpawn){let record=await claimOccurrence(options.dir,job,options.owner);if(record===void 0)return;let occurrence=job.fireCount+1,firedAt=options.now(),nextDueAt;if(job.everyMs!==null){nextDueAt=nextRecurringDueAt(job.dueAt,job.everyMs,firedAt);try{await rearmRecurringJob(options.dir,{...job,fireCount:occurrence,lastFiredAt:firedAt,dueAt:nextDueAt})}catch(error){throw await restorePending(options.dir,record,job.id),error}}let result;try{result=await options.deliver({type:"scheduled_prompt",id:job.id,sessionId:job.sessionId,...movedJobPaths(job),prompt:job.prompt,message:formatScheduledMessage(job,firedAt),dueAt:job.dueAt,firedAt,everyMs:job.everyMs,fireCount:occurrence},{onSpawn})}catch(error){result={ok:!1,error:error instanceof Error?error.message:String(error)}}return await settleOccurrence(options.dir,record,{...job,fireCount:occurrence,lastFiredAt:firedAt},occurrence,result),{event:"fired",id:job.id,sessionId:job.sessionId,occurrence,outcome:result.ok?"delivered":"failed",...result.ok?{}:{error:result.error},firedAt,dueAt:job.dueAt,...nextDueAt===void 0||await isCancelled(options.dir,job.id)?{}:{nextDueAt}}}async function runDueJobs(options){let listing=await listScheduledJobs(options.dir),events=listing.invalid.map(invalid=>({event:"invalid",...invalid})),jobs=options.sessionId===void 0?listing.jobs:listing.jobs.filter(record=>record.job.sessionId===options.sessionId),recovery=await recoverAbandoned(options,jobs);events.push(...recovery.events),await pruneTombstones(options.dir,listing,options.now());let nextDueAt,noteNext=dueAt=>{nextDueAt=nextDueAt===void 0?dueAt:Math.min(nextDueAt,dueAt)};for(let dueAt of recovery.restoredDueAt)noteNext(dueAt);let bySession=new Map;for(let{state,job}of jobs){if(state!=="pending")continue;if(job.dueAt>options.now()){noteNext(job.dueAt);continue}if(await isCancelled(options.dir,job.id))continue;let queue=bySession.get(job.sessionId)??[];queue.push(job),bySession.set(job.sessionId,queue)}let queues=[...bySession.values()],workers=Math.max(1,Math.min(options.concurrency??4,queues.length)),next=0;return await Promise.all(Array.from({length:workers},async()=>{for(;next<queues.length;){let queue=queues[next++]??[];for(let job of queue){let event;try{event=await fireOne(options,job)}catch(error){let message=error instanceof Error?error.message:String(error);events.push({event:"error",id:job.id,sessionId:job.sessionId,error:message}),noteNext(options.now()+DEFERRED_RETRY_MS);break}if(event!==void 0&&(events.push(event),event.event==="fired"&&event.nextDueAt!==void 0&¬eNext(event.nextDueAt),event.event==="deferred")){noteNext(options.now()+DEFERRED_RETRY_MS);break}}}})),{events,nextDueAt}}export{InvalidSidecarStoreError,UnsupportedSidecarStoreVersionError,encodedSessionId,sidecarFilePath,createSidecarStore,serializeByKey,scheduleDir,ensureDir,listScheduledJobs,createScheduledJob,cancelScheduledJob,readProcessStartMs,errorCode,unlinkIfPresent,readLeaseText,publishExclusive,publishReplace,reclaimLockState,breakStaleLock,reclaimInspected,RUNNER_HEARTBEAT_STALE_MS,RUNNER_HEARTBEAT_INTERVAL_MS,ownRunnerIdentity,writeRunnerLease,removeRunnerLease,liveRunners,isWatchRunnerAvailable,SessionHeldError,holdSessionFile,liveSessionHolders,withSessionMoveLock,deferWhileSessionOpen,execHookDelivery,sessionResumeDelivery,DEFERRED_RETRY_MS,runDueJobs};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{require_proper_lockfile}from"./chunk-GZVCRV7I.js";import{CONFIG_DIR_NAME,canonicalizePath,canonicalizePathStrict,findNodePackageDir,getDocsPath,getExamplesPath,getExportTemplateDir,getInstallPackageDir,getInteractiveAssetsDir,getPackageDir,getThemesDir,isBunBinary,resolvePath,stripBom}from"./chunk-SODMQEHO.js";import{__toESM}from"./chunk-IKOFCODM.js";var import_proper_lockfile=__toESM(require_proper_lockfile(),1);import{existsSync as existsSync2,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{readFile,realpath,stat}from"node:fs/promises";import{homedir}from"node:os";import{basename,dirname as dirname3,join as join3,relative,resolve as resolve2,sep as sep2}from"node:path";import{existsSync}from"node:fs";import{createRequire}from"node:module";import{dirname as dirname2,join as join2,resolve,sep}from"node:path";import{fileURLToPath as fileURLToPath2}from"node:url";import{dirname,join}from"node:path";import{fileURLToPath}from"node:url";function imagegenSkillPath(baseDir=dirname(fileURLToPath(import.meta.url))){return join(baseDir,"skill","SKILL.md")}var moduleRequire=createRequire(import.meta.url),runningFromSource=fileURLToPath2(import.meta.url).includes(`${sep}src${sep}core${sep}`),bundledBuiltinExtensions=[{id:"codemode",resolvePackage:()=>resolveBundledPackageJson("@code-yeongyu/senpi-codemode/package.json","senpi-codemode/package.json",join2("node_modules","@code-yeongyu","senpi-codemode","package.json"))}];function resolveBundledPackageJson(packageSpecifier,workspaceRelativePath,binaryRelativePath){let packageRoot=getPackageDir(),workspacePath=resolve(packageRoot,"..",workspaceRelativePath);if(runningFromSource&&existsSync(workspacePath))return workspacePath;let binaryPath=resolve(packageRoot,binaryRelativePath);if(isBunBinary&&existsSync(binaryPath))return binaryPath;try{return moduleRequire.resolve(packageSpecifier)}catch(error){if(existsSync(workspacePath))return workspacePath;throw error}}function getBundledResourceRoots(){let roots=[getThemesDir(),getExportTemplateDir(),getInteractiveAssetsDir(),getDocsPath(),getExamplesPath(),dirname2(imagegenSkillPath())];!isBunBinary&&!runningFromSource?roots.push(getPackageDir(),getInstallPackageDir(),findNodePackageDir(dirname2(fileURLToPath2(import.meta.url)))):isBunBinary&&roots.push(dirname2(fileURLToPath2(import.meta.url)));for(let extension of bundledBuiltinExtensions)try{roots.push(dirname2(extension.resolvePackage()))}catch(error){if(!(error instanceof Error&&"code"in error&&error.code==="MODULE_NOT_FOUND"))throw error}return roots}var TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES=["settings.json","mcp.json","extensions","skills","prompts","themes","SYSTEM.md","APPEND_SYSTEM.md"],LEGACY_PROJECT_CONFIG_DIR_NAME=".pi";function projectCodemodeNamesExecutable(configDir){let path=join3(configDir,"codemode.json");if(!existsSync2(path))return!1;let executableSettings=codemodeExecutableSettings();if(executableSettings===void 0)return!0;let parsed;try{parsed=JSON.parse(stripBom(readFileSync(path,"utf8")))}catch{return!0}return executableSettings.some(settingPath=>valueAtPath(parsed,settingPath)!==void 0)}function codemodeExecutableSettings(){let codemode=bundledBuiltinExtensions.find(extension=>extension.id==="codemode");try{let packageJson=codemode?.resolvePackage();if(packageJson===void 0)return;let list=JSON.parse(readFileSync(join3(dirname3(packageJson),"src","config","executable-settings.json"),"utf8"));if(typeof list!="object"||list===null)return;let paths=Reflect.get(list,"projectExecutableSettings");return Array.isArray(paths)&&paths.every(entry=>typeof entry=="string")?paths:void 0}catch{return}}function valueAtPath(value,path){let current=value;for(let key of path.split(".")){if(typeof current!="object"||current===null)return;current=Reflect.get(current,key)}return current}function normalizeCwd(cwd){let resolved=resolvePath(cwd);try{return canonicalizePathStrict(resolved)}catch{return resolved}}function findNearestTrustEntry(data,cwd){let currentDir=normalizeCwd(cwd);for(;;){let value=data[currentDir];if(value===!0||value===!1)return{path:currentDir,decision:value};let parentDir=dirname3(currentDir);if(parentDir===currentDir)return null;currentDir=parentDir}}function getProjectTrustParentPath(cwd){let trustPath=normalizeCwd(cwd),parentDir=dirname3(trustPath);return parentDir===trustPath?void 0:parentDir}function getProjectTrustOptions(cwd,options){let trustPath=normalizeCwd(cwd),trustOptions=[{label:"Trust",trusted:!0,updates:[{path:trustPath,decision:!0}],savedPath:trustPath}],parentPath=getProjectTrustParentPath(cwd);return parentPath!==void 0&&trustOptions.push({label:`Trust parent folder (${parentPath})`,trusted:!0,updates:[{path:parentPath,decision:!0},{path:trustPath,decision:null}],savedPath:parentPath}),options?.includeSessionOnly&&trustOptions.push({label:"Trust (this session only)",trusted:!0,updates:[]}),trustOptions.push({label:"Do not trust",trusted:!1,updates:[{path:trustPath,decision:!1}],savedPath:trustPath}),options?.includeSessionOnly&&trustOptions.push({label:"Do not trust (this session only)",trusted:!1,updates:[]}),trustOptions}function readTrustFile(path){if(!existsSync2(path))return{};let parsed;try{parsed=JSON.parse(stripBom(readFileSync(path,"utf-8")))}catch(error){let message=error instanceof Error?error.message:String(error);throw new Error(`Failed to read trust store ${path}: ${message}`)}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))throw new Error(`Invalid trust store ${path}: expected an object`);let data={};for(let[key,value]of Object.entries(parsed)){if(value!==!0&&value!==!1&&value!==null)throw new Error(`Invalid trust store ${path}: value for ${JSON.stringify(key)} must be true, false, or null`);data[key]=value}return data}function writeTrustFile(path,data){let sorted={};for(let key of Object.keys(data).sort()){let value=data[key];(value===!0||value===!1||value===null)&&(sorted[key]=value)}mkdirSync(dirname3(path),{recursive:!0}),writeFileSync(path,`${JSON.stringify(sorted,null,2)}
|
|
3
|
-
`,"utf-8")}function acquireTrustLockSync(path){let trustDir=dirname3(path);mkdirSync(trustDir,{recursive:!0});let maxAttempts=10,delayMs=20,lastError;for(let attempt=1;attempt<=maxAttempts;attempt++)try{return import_proper_lockfile.default.lockSync(trustDir,{realpath:!1,lockfilePath:`${path}.lock`})}catch(error){if((typeof error=="object"&&error!==null&&"code"in error?String(error.code):void 0)!=="ELOCKED"||attempt===maxAttempts)throw error;lastError=error;let start=Date.now();for(;Date.now()-start<delayMs;);}throw lastError instanceof Error?lastError:new Error("Failed to acquire trust store lock")}function withTrustFileLock(path,fn){let release=acquireTrustLockSync(path);try{return fn()}finally{release()}}function isHomeOrAboveDir(dir,homeDir){return dir===homeDir||homeDir.startsWith(dir.endsWith(sep2)?dir:`${dir}${sep2}`)}function findGitRepoRoot(dir){let currentDir=dir;for(;!existsSync2(join3(currentDir,".git"));){let parentDir=dirname3(currentDir);if(parentDir===currentDir)return;currentDir=parentDir}return currentDir}function projectAgentsSkillDirs(cwd){let homeDir=canonicalizePath(resolvePath(process.env.HOME||homedir())),startDir=resolvePath(cwd),gitRepoRoot=findGitRepoRoot(startDir),dirs=[],currentDir=startDir;for(;!isHomeOrAboveDir(canonicalizePath(currentDir),homeDir)&&(dirs.push(join3(currentDir,".agents","skills")),currentDir!==gitRepoRoot);){let parentDir=dirname3(currentDir);if(parentDir===currentDir)break;currentDir=parentDir}return dirs}function hasTrustRequiringProjectResources(cwd){let homeDir=canonicalizePath(resolvePath(process.env.HOME||homedir())),currentDir=canonicalizePath(resolvePath(cwd));return!isHomeOrAboveDir(currentDir,homeDir)&&([join3(currentDir,CONFIG_DIR_NAME),join3(currentDir,LEGACY_PROJECT_CONFIG_DIR_NAME)].some(configDir=>TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES.some(entry=>existsSync2(join3(configDir,entry))))||projectCodemodeNamesExecutable(join3(currentDir,CONFIG_DIR_NAME)))?!0:projectAgentsSkillDirs(cwd).some(dir=>existsSync2(dir))}var ProjectTrustStore=class{constructor(agentDir){this.trustPath=join3(resolvePath(agentDir),"trust.json")}get(cwd){return this.getEntry(cwd)?.decision??null}getEntry(cwd){return withTrustFileLock(this.trustPath,()=>{let data=readTrustFile(this.trustPath);return findNearestTrustEntry(data,cwd)})}set(cwd,decision){this.setMany([{path:cwd,decision}])}setMany(decisions){withTrustFileLock(this.trustPath,()=>{let data=readTrustFile(this.trustPath);for(let{path,decision}of decisions){let key=normalizeCwd(path);decision===null?delete data[key]:data[key]=decision}writeTrustFile(this.trustPath,data)})}},GITDIR_POINTER=/^gitdir:\s*(.+)$/;async function readFirstLine(path){try{return(await readFile(path,"utf-8")).split(/\r?\n/,1)[0]?.trim()||void 0}catch{return}}async function realPathOf(path){try{return await realpath(path)}catch{return}}async function entryKind(path){try{let stats=await stat(path);return stats.isDirectory()?"directory":stats.isFile()?"file":void 0}catch{return}}async function linkedWorktreeMainCheckoutDir(cwd){let dir=await realPathOf(resolvePath(cwd))??resolvePath(cwd),checkoutRoot=dir,markerKind=await entryKind(join3(checkoutRoot,".git"));for(;markerKind===void 0;){let parentDir=dirname3(checkoutRoot);if(parentDir===checkoutRoot)return;checkoutRoot=parentDir,markerKind=await entryKind(join3(checkoutRoot,".git"))}if(markerKind!=="file")return;let marker=join3(checkoutRoot,".git"),adminTarget=GITDIR_POINTER.exec(await readFirstLine(marker)??"")?.[1]?.trim();if(!adminTarget)return;let adminDir=await realPathOf(resolve2(checkoutRoot,adminTarget));if(adminDir===void 0)return;let commonDirPointer=await readFirstLine(join3(adminDir,"commondir")),backPointer=await readFirstLine(join3(adminDir,"gitdir"));if(commonDirPointer===void 0||backPointer===void 0)return;let markerRealPath=await realPathOf(marker);if(markerRealPath===void 0||await realPathOf(resolve2(adminDir,backPointer))!==markerRealPath)return;let commonDir=await realPathOf(resolve2(adminDir,commonDirPointer));if(commonDir===void 0||basename(commonDir)!==".git"||dirname3(adminDir)!==join3(commonDir,"worktrees"))return;let mainCheckout=dirname3(commonDir),mainMarker=join3(mainCheckout,".git");if(!(await entryKind(mainMarker)!=="directory"||await realPathOf(mainMarker)!==commonDir))return join3(mainCheckout,relative(checkoutRoot,dir))}async function getSavedProjectTrust(store,cwd){let own=store.get(cwd);if(own!==null)return own;let mainCheckoutDir=await linkedWorktreeMainCheckoutDir(cwd);return mainCheckoutDir===void 0?null:store.get(mainCheckoutDir)}export{imagegenSkillPath,bundledBuiltinExtensions,getBundledResourceRoots,getProjectTrustOptions,projectAgentsSkillDirs,hasTrustRequiringProjectResources,ProjectTrustStore,getSavedProjectTrust};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{allocateMonitorId,endpointSockets,matchesTask,subscriptionFor,wantsRecord}from"./chunk-POZMRM6V.js";import"./chunk-Y7Z7XPUF.js";import{connectAuthenticatedRpcSocket,socketNeedsHandshake}from"./chunk-2CNSCMYC.js";import"./chunk-YI6KRJDC.js";import{OBSERVE_REQUEST_FIELD}from"./chunk-DBQBJTMF.js";import{MAX_RPC_LINE_CHARACTERS,attachJsonlLineReader}from"./chunk-SF4MQDKZ.js";import"./chunk-RUGTHELO.js";import{readSocketSecret,resolveSocketTransportAddress,sendSocketHandshake,socketSecretPath}from"./chunk-HW6AAS3R.js";import"./chunk-SODMQEHO.js";import"./chunk-ATQFN5HI.js";import"./chunk-O5RYI3IW.js";import"./chunk-IKOFCODM.js";var DELTA_KINDS=new Set(["message_update","tool_execution_update","bash_execution_update"]),SURFACE_KINDS=new Set(["loaded_surfaces_changed","commands_changed"]),LAST_TEXT_LIMIT=60,RpcMonitorCoalescer=class{#windows=new Map;#flush;constructor(flush){this.#flush=flush}accept(sessionKey2,label,record){let type=String(record.type),delta=DELTA_KINDS.has(type);if(!delta&&!SURFACE_KINDS.has(type))return!1;let key=delta?`${sessionKey2}#deltas`:`${sessionKey2}#${type}`,open=this.#windows.get(key),last=delta?lastText(record):"";if(open!==void 0)return open.count+=1,last!==""&&(open.last=last),!0;let window={count:1,last,timer:setTimeout(()=>{this.#windows.delete(key),this.#flush(delta?`rpc ${label()} streaming +${window.count} deltas${window.last===""?"":` (last: "${window.last}")`}`:`rpc ${label()} ${type} x${window.count}`)},delta?2e3:5e3)};return this.#windows.set(key,window),!0}dispose(){for(let window of this.#windows.values())clearTimeout(window.timer);this.#windows.clear()}};function lastText(record){let event=record.assistantMessageEvent,flat=(event?.type==="text_delta"&&typeof event.delta=="string"?event.delta:typeof record.output=="string"?record.output:"").replace(/\s+/g," ").trim();return flat.length>LAST_TEXT_LIMIT?flat.slice(-LAST_TEXT_LIMIT):flat}import{createConnection}from"node:net";var SUBSCRIBE_ID="monitor-subscribe";async function openRpcObserver(socketPath,subscription,handlers,options={}){let secret=socketNeedsHandshake(socketPath)?await readSocketSecret(socketSecretPath(socketPath)):void 0,authenticated=socketNeedsHandshake(socketPath)?void 0:await connectAuthenticatedRpcSocket(socketPath,void 0,options),socket=authenticated?.socket??createConnection(resolveSocketTransportAddress(socketPath,process.platform,secret)),answer=Promise.withResolvers(),ended=!1,end=reason=>{ended||(ended=!0,answer.reject(new Error(`rpc monitor: ${socketPath}: ${reason}`)),handlers.closed(reason))},subscribe=()=>{secret!==void 0&&sendSocketHandshake(socket,secret);let frame={...subscription,[OBSERVE_REQUEST_FIELD]:!0,id:SUBSCRIBE_ID,type:"subscribe_sessions"};socket.write(`${JSON.stringify(frame)}
|
|
3
|
-
`)},detach=attachJsonlLineReader(socket,line=>{let record;try{record=JSON.parse(line)}catch{return}if(record.type==="response"&&record.id===SUBSCRIBE_ID){record.success===!0?answer.resolve(record):(answer.reject(new Error(`rpc monitor: ${socketPath} refused the subscription: ${String(record.error)}`)),socket.destroy());return}handlers.record(record)},{maxLineLength:MAX_RPC_LINE_CHARACTERS});return socket.on("error",error=>end(error.message)),socket.once("close",()=>{detach(),end("connection closed")}),authenticated?subscribe():socket.once("connect",subscribe),{subscribed:answer.promise,close:()=>socket.destroy()}}var RpcMonitorRenderer=class{#sessions=new Map;#namedThisBatch=new Set;seed(socket,snapshot){return snapshot.map(row=>{let view=this.#view(socket,row);view.status=String(row.status);let model=modelId(row.model),attachments=typeof row.attachments=="number"?row.attachments:0;return`${view.label} ${view.status} (${attachments} attached${model===void 0?"":`, model ${model}`})`})}forgetSocket(socket){let count2=0;for(let key of[...this.#sessions.keys()])key.startsWith(`${socket}#`)&&(this.#sessions.delete(key),count2++);return count2}statuses(){return new Map([...this.#sessions].map(([key,view])=>[key,view.status]))}label(socket,record){let view=this.#view(socket,record),key=sessionKey(socket,record);return this.#namedThisBatch.has(key)?view.label:(this.#namedThisBatch.size===0&&queueMicrotask(()=>this.#namedThisBatch.clear()),this.#namedThisBatch.add(key),`${view.label} (${String(record.sessionId)})`)}render(socket,record){switch(record.type){case"session_status":return this.#status(socket,record);case"effect_status":{let detail=record.disposition??reasonOf(record.outcome);return`rpc ${this.label(socket,record)} effect ${String(record.kind)} ${tail(String(record.effectId))} ${String(record.state)}${detail===void 0?"":` (${String(detail)})`}`}case"agent_end":return`rpc ${this.label(socket,record)} agent_end${record.aborted===!0?` aborted(${String(record.abortSource??"user")})`:""}`;case"model_changed":return`rpc ${this.label(socket,record)} model_changed -> ${modelId(record.model)??"?"}${record.source===void 0?"":` (${String(record.source)})`}`;case"queue_update":return`rpc ${this.label(socket,record)} queue_update steering=${count(record.steering)} followUp=${count(record.followUp)}`;case"session_replaced":return`rpc ${this.label(socket,record)} session_replaced -> ${String(record.durableSessionId??record.newSessionId??"?")}`;case"session_closed":return`rpc ${this.label(socket,record)} session_closed ${String(record.reason??"")}`.trimEnd();case"extension_event":return`rpc ${this.label(socket,record)} extension_event ${String(record.name??record.event??"")}`.trimEnd();case"entry_appended":return`rpc ${this.label(socket,record)} entry_appended ${entryType(record)}`;case"tool_execution_start":case"tool_execution_end":return`rpc ${this.label(socket,record)} tool ${String(record.toolName??"?")} ${record.type==="tool_execution_start"?"started":"ended"}`;case"turn_end":case"turn_start":case"agent_start":case"agent_idle":case"agent_settled":return`rpc ${this.label(socket,record)} ${record.type}`;default:{let{type:_type,sessionId:_sessionId,...rest}=record,json=JSON.stringify(rest),body=json.length>160?`${json.slice(0,160)}...`:json;return`rpc ${this.label(socket,record)} ${String(record.type)} ${body}`}}}#status(socket,record){let view=this.#view(socket,record),to=String(record.status),from=view.status;if(view.status=to,from===to)return;let change=record.lastChange,why=change===void 0?"":` (${String(change.cause)}, by ${String(change.by)})`;return`rpc ${this.label(socket,record)} status ${from} -> ${to}${why} seq=${String(record.seq)}`}#view(socket,record){let key=sessionKey(socket,record),view=this.#sessions.get(key);return view===void 0?(view={label:labelOf(record),status:"unknown"},this.#sessions.set(key,view)):(typeof record.name=="string"||contextTask(record)!==void 0)&&(view.label=labelOf(record)),view}};function sessionKey(socket,record){return`${socket}#${String(record.sessionId)}`}function labelOf(record){let task=contextTask(record);return task!==void 0?task:typeof record.name=="string"&&record.name!==""?record.name:(typeof record.durableSessionId=="string"?record.durableSessionId:String(record.sessionId)).slice(0,8)}function contextTask(record){let context=record.context;return typeof context?.task_id=="string"?context.task_id:void 0}function modelId(model){return typeof model=="object"&&model!==null&&"id"in model?String(model.id):void 0}function reasonOf(outcome){return typeof outcome=="object"&&outcome!==null&&"reason"in outcome?String(outcome.reason):void 0}function count(value){return Array.isArray(value)?value.length:typeof value=="number"?value:0}function tail(id){return id.length>8?id.slice(-8):id}function entryType(record){let entry=record.entry;return String(entry?.type??record.entryType??"?")}var RPC_RESCAN_MS=3e4,RpcMonitorWatch=class{#options;#observers=new Map;#hosts=new Map;#renderer=new RpcMonitorRenderer;#coalescer;#taskSessions=new Set;#rescan;#stopped=!1;constructor(options){this.#options=options,this.#coalescer=new RpcMonitorCoalescer(options.line)}async start(){let rows=await this.#join();return this.#rescan=setInterval(()=>{this.#join()},RPC_RESCAN_MS),this.#rescan.unref?.(),rows}checkpoint(){return{hosts:Object.fromEntries(this.#hosts),statuses:Object.fromEntries(this.#renderer.statuses())}}diffSince(saved){let lines=[];for(let[socket,host]of this.#hosts){let before=saved.hosts[socket];before!==void 0&&before.generation!==host.generation&&lines.push(`rpc host ${socket} restarted (generation ${before.generation} -> ${host.generation})`)}let now=this.#renderer.statuses();for(let[key,status]of Object.entries(saved.statuses)){let label=key.slice(key.lastIndexOf("#")+1).slice(0,8),current=now.get(key),socket=key.slice(0,key.lastIndexOf("#"));current===void 0?lines.push(this.#hosts.has(socket)&&this.#options.target.kind!=="children"?`rpc ${label} closed (gone while detached)`:this.#hosts.has(socket)?`rpc ${label} no longer listed (closed or no longer a child while detached)`:`rpc ${label} unreachable (its host did not answer)`):current!==status&&lines.push(`rpc ${label} status ${status} -> ${current} (changed while detached)`)}return lines}stop(){this.#stopped=!0,this.#rescan!==void 0&&clearInterval(this.#rescan),this.#coalescer.dispose();for(let observer of this.#observers.values())observer.close();this.#observers.clear()}async#join(){let fresh=(await(this.#options.endpoints??(()=>endpointSockets(this.#options.agentDir)))().catch(()=>[])).filter(socket=>!this.#observers.has(socket));return(await Promise.all(fresh.map(socket=>this.#observe(socket)))).flat()}async#observe(socket){if(this.#stopped)return[];let{target,events}=this.#options,observer;try{observer=await openRpcObserver(socket,subscriptionFor(target,events),{record:record=>{this.#stopped||this.#record(socket,record)},closed:()=>this.#lost(socket,observer)})}catch{return[]}if(this.#stopped)return observer.close(),[];this.#observers.set(socket,observer);let answer=await observer.subscribed.catch(()=>{});if(this.#stopped)return observer.close(),[];let data=answer?.data;if(data===void 0)return[];this.#hosts.set(socket,{instanceId:String(data.hostInstanceId),generation:String(data.hostGeneration)});let matched=(Array.isArray(data.snapshot)?data.snapshot:[]).filter(row=>matchesTask(target,row));for(let row of matched)this.#taskSessions.add(sessionKey(socket,row));if(target.kind!=="all"&&target.kind!=="children"&&matched.length===0)return observer.close(),this.#observers.delete(socket),[];let rows=this.#renderer.seed(socket,matched);return this.#options.checkpoint?.(this.checkpoint()),rows}#lost(socket,observer){if(this.#observers.get(socket)!==observer||(this.#observers.delete(socket),this.#stopped))return;let forgotten=this.#renderer.forgetSocket(socket);for(let key of[...this.#taskSessions])key.startsWith(`${socket}#`)&&this.#taskSessions.delete(key);(this.#hosts.delete(socket)||forgotten>0)&&(this.#options.line(`rpc host ${socket} gone (${forgotten} session${forgotten===1?"":"s"} no longer watched)`),this.#options.checkpoint?.(this.checkpoint()))}#record(socket,record){let{target,events,exclude}=this.#options,type=String(record.type);if(!wantsRecord(events,exclude,type))return;let key=sessionKey(socket,record);if(target.kind==="task"&&(matchesTask(target,record)&&this.#taskSessions.add(key),!this.#taskSessions.has(key))||this.#coalescer.accept(key,()=>this.#renderer.label(socket,record),record))return;let line=this.#renderer.render(socket,record);line!==void 0&&(this.#options.line(line),type==="session_status"&&this.#options.checkpoint?.(this.checkpoint()))}};var RPC_WATCHER_TIMED_OUT_SUMMARY="watcher timed_out",RpcMonitorSource=class{#records=new Map;#host;#nextId=1;constructor(host){this.#host=host}get(id){return this.#records.get(id)}values(){return this.#records.values()}keys(){return this.#records.keys()}async register(options){let id=`rpc_${this.#nextId++}`,monitorId=options.monitorId??allocateMonitorId(),watch=new RpcMonitorWatch({agentDir:options.agentDir,target:options.target,events:options.events,exclude:options.exclude,...options.endpoints===void 0?{}:{endpoints:options.endpoints},line:line=>this.#line(id,line),...options.onCheckpoint===void 0?{}:{checkpoint:checkpoint=>options.onCheckpoint?.(monitorId,checkpoint)}}),record={id,monitorId,description:options.description,startedAtMs:Date.now(),command:null,filter:options.filter,persistent:options.persistent,deadlineMs:options.persistent||options.timeoutMs===void 0?null:Date.now()+options.timeoutMs,fireCount:0,lastFiredAtMs:null,expiresAt:options.expiresAt,paused:!1,settled:!1,rpcWatch:watch,targetText:options.targetText,mutedDropped:0,lastLine:void 0,deadline:options.persistent||options.timeoutMs===void 0?void 0:setTimeout(()=>this.settle(id,RPC_WATCHER_TIMED_OUT_SUMMARY),options.timeoutMs)};this.#records.set(id,record),this.#host.changed();let rows=await watch.start();if(options.restoredFrom!==void 0)for(let line of watch.diffSince(options.restoredFrom))this.#line(id,line);return{id,monitorId,rows}}settle(id,summary){let record=this.#records.get(id);return record===void 0||record.settled?!1:(record.settled=!0,record.rpcWatch.stop(),clearTimeout(record.deadline),this.#records.delete(id),this.#host.changed(),summary!=="watcher disposed"&&this.#host.emit(record,{type:"summary",id,description:record.description,summary}),this.#host.ended(record,summary===RPC_WATCHER_TIMED_OUT_SUMMARY?"timeout":summary==="watcher disposed"?"disposed":"killed",null),!0)}settleAll(summary){let ids=[...this.#records.keys()];for(let id of ids)this.settle(id,summary);return ids.length}#line(id,line){let record=this.#records.get(id);if(!(record===void 0||record.settled)&&!(record.filter instanceof RegExp&&!record.filter.test(line))&&line!==record.lastLine){if(record.lastLine=line,record.paused){record.mutedDropped+=1;return}this.#host.emit(record,{type:"line",id,description:record.description,line})}}};export{RpcMonitorSource};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{SessionEventWriter,answerSubscribeSessions,answerUnsubscribeSessions,presenterContextReply,refusal,sessionRuntimeView,socketSink,startMcpPresenterControl}from"./chunk-KCGUUIUS.js";import{CONNECTION_ROLE_FIXED,ConnectionRoles,TerminalSealedSecrets,WakeScheduler,answerGetEntries,answeredUiRequestId,availableModelsData,buildRpcSessionState,getSnapshot,interruptRunningTurn,onProcessContinue,rpcCommandShapeError,runAbort,runGetEffectStatus,sessionUiAnswerLedger,settledQuestionStatus,subscribeSessionRecords,unansweredQuestionIds,unsupportedThinkingLevel,watchInbox}from"./chunk-H2FBGZNP.js";import"./chunk-FUQJK2TU.js";import"./chunk-436DNN4Z.js";import"./chunk-J2I56WLK.js";import"./chunk-TQHNFP77.js";import"./chunk-C7HEEBFH.js";import"./chunk-MOVB6H22.js";import"./chunk-YP47G4YP.js";import{ENTRIES_TAIL_CAPABILITY,INTERACTIVE_USER_CAPABILITY,OBSERVE_READS_CAPABILITY,SESSION_STATUS_CAPABILITY,TUI_OBSERVER_CAPABILITY,engineBuildIdentity,gcHostEndpoints}from"./chunk-HHSYTDKO.js";import"./chunk-QMRJYMNH.js";import"./chunk-Y7Z7XPUF.js";import"./chunk-54YTJCYZ.js";import"./chunk-26UBFF6N.js";import"./chunk-I5ASHSLZ.js";import{tuiSocketName}from"./chunk-2CNSCMYC.js";import"./chunk-7WYJAYHH.js";import{acquireHostEnsureLock,thisProcessStartTime,writeHostRegistration}from"./chunk-EAEEM2KN.js";import{createDaemonDirectories,createHostDaemonPaths}from"./chunk-YI6KRJDC.js";import"./chunk-DBQBJTMF.js";import{MAX_RPC_LINE_CHARACTERS,attachJsonlLineReader,serializeJsonLine}from"./chunk-SF4MQDKZ.js";import"./chunk-GZVCRV7I.js";import"./chunk-KUPQO7MC.js";import"./chunk-U3VOXCGP.js";import"./chunk-6VWIMHMH.js";import"./chunk-YUVC7SJB.js";import"./chunk-COH5F7QV.js";import"./chunk-RFB5CBW7.js";import"./chunk-Y7QPP7BG.js";import"./chunk-ZDHAJROE.js";import"./chunk-GVSBCJFI.js";import"./chunk-Z6TEQEZO.js";import"./chunk-HJDTYAPV.js";import"./chunk-WHPDEDCC.js";import"./chunk-HK46JMSC.js";import"./chunk-WIOV3AXZ.js";import"./chunk-WDTJ6JKE.js";import"./chunk-5ROMKDLK.js";import"./chunk-3W47LTXQ.js";import"./chunk-OKMHSVCA.js";import"./chunk-JGKH3D6W.js";import"./chunk-3SASTB6Y.js";import"./chunk-NDNQ23SK.js";import"./chunk-PJ34QVL5.js";import"./chunk-RUGTHELO.js";import"./chunk-42XRJQM6.js";import{MAX_SOCKET_PATH_BYTES}from"./chunk-5XHOHZIR.js";import{authenticateSocket,createSocketSecret,resolveSocketTransportAddress,socketSecretPath}from"./chunk-HW6AAS3R.js";import{VERSION}from"./chunk-SODMQEHO.js";import"./chunk-ATQFN5HI.js";import"./chunk-MIENF2YK.js";import"./chunk-O5RYI3IW.js";import"./chunk-IKOFCODM.js";import{randomUUID as randomUUID2}from"node:crypto";import{rm as rm3}from"node:fs/promises";var ControlAttachments=class{constructor(session,pendingQuestions,presenters){this.writer=new SessionEventWriter(()=>{}),this.roles=new ConnectionRoles,this.receipts=new Map,this.attached=new Set,this.session=session,this.presenters=presenters,this.writer.status.setSource({sessionIds:()=>[this.handle],entryView:id=>id===this.handle?this.entryView():void 0,view:id=>id===this.handle?{entry:this.entryView(),runtime:sessionRuntimeView(session,pendingQuestions())}:void 0}),this.writer.registerConnection(NULL_CONNECTION,{writeRaw:()=>{},waitForBackpressure:async()=>{}}),this.stopRecords=subscribeSessionRecords(session,record=>this.writer.enqueue(this.handle,record))}get handle(){return this.session.sessionId}opened(connection){this.writer.registerConnection(keyOf(connection),connection.transport),this.presenters?.opened(keyOf(connection),connection.transport)}closed(connection){let key=keyOf(connection);this.attached.delete(key)&&this.writer.status.publish(this.handle,"detach","client"),this.roles.release(key),this.presenters?.closed(key),this.writer.unregisterConnection(key)}detach(connection){let key=keyOf(connection);return this.attached.delete(key)?(this.presenters?.detach(key),this.writer.detachConnectionFromSession(key,this.handle),this.writer.status.publish(this.handle,"detach","client"),!0):!1}attach(connection){let key=keyOf(connection);return this.attached.has(key)?!1:(this.attached.add(key),this.presenters?.attach(key),this.writer.attachConnectionToSession(key,this.handle),this.writer.status.publish(this.handle,"attach","client"),!0)}isAttached(connection){return this.attached.has(keyOf(connection))}run(connection,task){return this.writer.withConnection(keyOf(connection),task)}respond(connection,answer2){this.writer.enqueueControl(answer2,keyOf(connection)).catch(()=>connection.transport.close?.())}dispose(){this.stopRecords()}entryView(){let manager=this.session.sessionManager,path=this.session.sessionFile,name=manager.getSessionName();return{state:"open",kind:"interactive",cwd:manager.getCwd(),attachments:1+this.attached.size,durableSessionId:this.session.sessionId,...path!==void 0?{sessionPath:path}:{},...name!==void 0?{name}:{},context:{}}}},NULL_CONNECTION="tui-null";function keyOf(connection){return`tui-${connection.id}`}import{realpathSync}from"node:fs";import{resolve}from"node:path";import{randomUUID}from"node:crypto";function idOf(command){return typeof command.id=="string"?command.id:void 0}function answered(id,command,data){return{...id===void 0?{}:{id},type:"response",command,success:!0,data}}function admitPrompt(context,command){let behavior=command.streamingBehavior;return behavior===void 0&&context.session.isStreaming?refusal(idOf(command),"prompt","session_busy",{reason:"a turn is running and the prompt names no streamingBehavior",action:'send it with streamingBehavior "steer" or "followUp", or as a steer/follow_up'}):admitInput(context,command,behavior==="followUp"?"followUp":"steer","start")}var UNSERVED_INPUT_FIELDS=["images","thinkingLevel","expandPromptTemplates","unknownCommandAsText","sessionTitlePrompt","enqueueOrder","chainCount"];function admitInput(context,command,deliverAs,whenIdle){let id=idOf(command),unserved=UNSERVED_INPUT_FIELDS.filter(field=>command[field]!==void 0);if(unserved.length>0)return refusal(id,command.type,"unsupported_field",{reason:`a terminal delivers input through its admission, which carries text only, not ${unserved.join(", ")}`,action:"send the message without those fields, or send it to a host session"});let message=command.message;if(typeof message!="string"||message.trim()==="")return refusal(id,command.type,"invalid_message",{reason:"message is empty",action:"send a non-empty message"});if(whenIdle!==void 0&&whenIdle!=="queue"&&whenIdle!=="start")return refusal(id,command.type,"invalid_when_idle",{reason:`whenIdle must be "queue" or "start", not ${JSON.stringify(whenIdle)}`,action:'send whenIdle: "queue" (the default) or "start"'});let admission=context.session.externalAdmission,clientMessageId=typeof command.clientMessageId=="string"?command.clientMessageId:void 0,fingerprint=JSON.stringify([command.type,message,deliverAs,whenIdle??null]),prior=clientMessageId===void 0?void 0:context.attachments.receipts.get(clientMessageId);if(prior!==void 0)return prior.fingerprint!==fingerprint?refusal(id,command.type,"client_message_id_conflict",{reason:`clientMessageId ${clientMessageId} was already used for different input`,action:"send new input with a new clientMessageId"}):answered(id,command.type,{disposition:prior.disposition,clientMessageId,replayed:!0,duplicate:!0,admission:prior.admission});let expected=typeof command.expected_turn_id=="number"?command.expected_turn_id:admission.turnEpoch,result=admission.admit({delivery_id:clientMessageId??`tui-rpc-${randomUUID()}`,text:message,sender:{kind:"external",platform:"rpc"},deliverAs,expected_turn_id:expected,whenIdle:whenIdle==="start"?"start":"queue"}),identity=clientMessageId===void 0?{}:{clientMessageId},admitted=disposition=>{if(clientMessageId===void 0)return answered(id,command.type,{disposition});let receipt={fingerprint,disposition,admission:{clientMessageId,durableSessionId:context.session.sessionId,disposition}};return context.attachments.receipts.set(clientMessageId,receipt),answered(id,command.type,{disposition,clientMessageId,admission:receipt.admission})};switch(result.kind){case"started":return admitted("started");case"steered":case"queued":return admitted("queued");case"already_admitted":return answered(id,command.type,{disposition:"queued",duplicate:!0,...identity});case"held_draft":return refusal(id,command.type,"held_draft",{reason:"the user is composing in this terminal, so nothing was delivered",action:"retry after the draft is sent or cleared"});case"turn_conflict":return refusal(id,command.type,"turn_conflict",{reason:`the turn moved on (now ${result.turn_epoch}); this steer named an earlier one`,action:"read get_state's turn_epoch and send it again"})}}import{chmod}from"node:fs/promises";import{createServer}from"node:net";async function listenControlSocket(socketPath,secret,handlers){let clients=new Set,nextConnection=0,server=createServer(socket=>{clients.add(socket),socket.once("close",()=>clients.delete(socket)),socket.on("error",()=>socket.destroy()),authenticateSocket(socket,secret,()=>acceptConnection(socket,++nextConnection,handlers))});return await new Promise((resolve2,reject)=>{server.once("error",reject),server.listen(resolveSocketTransportAddress(socketPath,process.platform,secret),()=>{server.off("error",reject),resolve2()})}),server.on("error",error=>handlers.listenerError(error)),process.platform!=="win32"&&await chmod(socketPath,384),{close:()=>closeServer(server,clients)}}function acceptConnection(socket,id,handlers){let connection={id,send:record=>{handlers.respond!==void 0?handlers.respond(connection,record):socket.destroyed||socket.write(serializeJsonLine(record))},transport:socketSink(socket)};handlers.opened?.(connection);let detach=attachJsonlLineReader(socket,line=>{answer(connection,line,handlers)},{maxLineLength:MAX_RPC_LINE_CHARACTERS,onOversizedLine:()=>connection.send(failure(void 0,"parse","RPC line exceeds the size limit."))});socket.once("close",()=>{detach(),handlers.closed(connection)})}async function answer(connection,line,handlers){let parsed;try{parsed=JSON.parse(line)}catch{connection.send(failure(void 0,"parse","Invalid JSON."));return}let shapeError=rpcCommandShapeError(parsed);if(shapeError!==void 0||!isCommand(parsed)){connection.send(failure(void 0,"parse",shapeError??"RPC command needs a string `type`."));return}let answer2=await handlers.command(connection,parsed);answer2!==null&&connection.send(answer2)}function isCommand(value){return typeof value=="object"&&value!==null&&"type"in value&&typeof value.type=="string"}function failure(id,command,error){return{...id===void 0?{}:{id},type:"response",command,success:!1,error}}function success(id,command,data){return{...id===void 0?{}:{id},type:"response",command,success:!0,...data===void 0?{}:{data}}}function closeServer(server,clients){for(let client of clients)client.destroy();return new Promise(resolve2=>server.close(()=>resolve2()))}import{rm}from"node:fs/promises";var TERMINAL_PRESENTER_COMMANDS=["mcp_presenter_context","mcp_form_presented","mcp_form_response","mcp_form_sync","mcp_presenter_secret_answer"];async function startTerminalPresenters(presenter){let surface=new TerminalPresenterSurface(presenter.local),started=await startMcpPresenterControl(surface,{enforcement:!0,account:presenter.account});return{surface,dispose:async()=>{started?.control.dispose(),started?.dir&&await rm(started.dir,{recursive:!0,force:!0})}}}function presents(capabilities){return capabilities.includes("interactive_user")&&capabilities.includes("mcp_form_presenter_v1")}function writePrivate(transport,record){try{return transport.writeRaw(serializeJsonLine(record)),!0}catch{return!1}}var TerminalPresenterSurface=class{constructor(local){this.sharedAuthority=!0,this.peers=new Map,this.local=local,this.authority=local.authority,this.binding=local.binding,this.sessionId=local.sessionId}attachSealedSecrets(source){let sealed=new TerminalSealedSecrets(source,this.sessionId,this.local.admit);return this.local.sealed=sealed,()=>{this.local.sealed===sealed&&(this.local.sealed=void 0),sealed.dispose()}}getMcpFormBinding(sessionId){return sessionId===this.sessionId?this.binding:void 0}findMcpPresenterConnection(nonce){for(let peer of this.peers.values())if(peer.sender?.nonce===nonce)return peer.sender}getConnectionCapabilities(id){return this.peers.get(id)?.capabilities}advertiseMcpPresenterControl(path){this.controlEndpoint=path}withdrawMcpPresenterControl(path){this.controlEndpoint===path&&(this.controlEndpoint=void 0)}opened(id,transport){this.peers.set(id,{transport,capabilities:[],attached:!1})}declare(id,capabilities){let peer=this.peers.get(id);if(peer){if(peer.capabilities=[...capabilities],!presents(capabilities)){peer.sender&&this.authority.withdraw(peer.sender);return}if(!peer.sender){let sender=this.authority.connect(id,frame=>this.peers.get(id)!==peer||!writePrivate(peer.transport,frame)?(this.authority.detach(sender),!1):(peer.transport.waitForBackpressure().catch(()=>this.authority.detach(sender)),!0));peer.sender=sender}peer.attached&&this.authority.attach(peer.sender,this.binding)}}attach(id){let peer=this.peers.get(id);peer&&(peer.attached=!0,peer.sender&&this.authority.attach(peer.sender,this.binding))}detach(id){let peer=this.peers.get(id);peer&&(peer.attached=!1,peer.sender&&this.authority.detach(peer.sender,this.binding))}closed(id){let sender=this.peers.get(id)?.sender;this.peers.delete(id),sender&&this.authority.detach(sender)}receive(id,frame){let peer=this.peers.get(id);if(!peer)return;if(frame.type==="mcp_presenter_secret_answer"){let lineId=typeof frame.id=="string"?{id:frame.id}:{};(this.local.sealed?.receive(id,frame)??Promise.resolve({error:"secret_answer_refused"})).catch(()=>({error:"secret_answer_refused"})).then(reply2=>{!writePrivate(peer.transport,{type:"mcp_presenter_secret_reply",...lineId,...reply2})&&peer.sender&&this.authority.detach(peer.sender)});return}let sender=peer.sender,reply=frame.type==="mcp_presenter_context"?presenterContextReply(this.authority,sender,frame,this.controlEndpoint):sender?this.authority.receive(sender,frame):{error:"presenter_not_authorized"},answer2={type:"mcp_form_reply",...typeof frame.id=="string"?{id:frame.id}:{},...reply};!writePrivate(peer.transport,answer2)&&sender&&this.authority.detach(sender)}};var TUI_RPC_COMMANDS=["open_session","get_snapshot","subscribe_sessions","unsubscribe_sessions","steer","follow_up","prompt","get_effect_status","set_client_info","attach_session","release_attachment","abort",...TERMINAL_PRESENTER_COMMANDS],STOP_REQUIRES_INTERACTIVE_CLIENT="stop_requires_interactive_client: declare interactive_user with set_client_info first";async function runTuiRpcCommand(context,connection,command){switch(command.type){case"get_snapshot":{let{attachments,session}=context;if(command.sessionId!==void 0&&command.sessionId!==attachments.handle)return{id:command.id,type:"response",command:command.type,success:!1,error:"unknown_session"};let snapshot=getSnapshot(session,{tail:command.tail,maxBytes:command.maxBytes},{enqueue:markerId=>attachments.run(connection,()=>attachments.writer.enqueue(attachments.handle,{type:"snapshot_marker",markerId}))});return"error"in snapshot?{id:command.id,type:"response",command:command.type,success:!1,error:snapshot.error}:answered(idOf(command),command.type,snapshot.data)}case"open_session":return openSession(context,connection,command);case"subscribe_sessions":return subscribe(context,connection,command);case"unsubscribe_sessions":return answerUnsubscribeSessions(command,keyOf(connection),context.attachments.writer.status);case"steer":case"follow_up":return admitInput(context,command,command.type==="steer"?"steer":"followUp",command.whenIdle);case"prompt":return admitPrompt(context,command);case"set_client_info":return setClientInfo(context,connection,command);case"attach_session":return attachSession(context,connection,command);case"release_attachment":return releaseAttachment(context,connection,command);case"abort":return abort(context,connection,command);case"mcp_presenter_context":case"mcp_form_presented":case"mcp_form_response":case"mcp_form_sync":case"mcp_presenter_secret_answer":return context.attachments.presenters?(context.attachments.presenters.receive(keyOf(connection),command),null):void 0;case"get_effect_status":return runGetEffectStatus({session:context.session,by:{connectionId:keyOf(connection)},sessionId:context.attachments.handle},command);default:return}}function openSession(context,connection,command){let{session,attachments}=context,ownPath=typeof command.sessionPath=="string"&&samePath(command.sessionPath,session.sessionFile),ownId=command.sessionId!==void 0&&command.sessionId===session.sessionId;return!ownPath&&!ownId?refusal(idOf(command),"open_session","terminal_single_session",{reason:"this terminal hosts one session and the request names another",action:"open it by this terminal's sessionPath, or open the other session on a host"}):(attachments.run(connection,()=>{attachments.attach(connection),attachments.writer.enqueue(attachments.handle,{id:command.id,type:"response",command:"open_session",success:!0,data:{sessionId:attachments.handle,state:buildRpcSessionState(session),attached:!0}})}),null)}function setClientInfo(context,connection,command){let capabilities=command.capabilities;return capabilities===void 0?success(command.id,"set_client_info"):!Array.isArray(capabilities)||!capabilities.every(entry=>typeof entry=="string")?failure(command.id,"set_client_info","capabilities must be an array of strings"):context.attachments.roles.declare(keyOf(connection),capabilities)!=="declared"?failure(command.id,"set_client_info",CONNECTION_ROLE_FIXED):(context.attachments.presenters?.declare(keyOf(connection),capabilities),success(command.id,"set_client_info"))}function attachSession(context,connection,command){let{session,attachments}=context,id=idOf(command);if(command.sessionId!==attachments.handle)return refusal(id,"attach_session","unknown_session",{reason:"this terminal hosts one session and the request names another",action:"attach with this terminal's sessionId from list_sessions"});let instanceMatches=command.expectedHostInstance===void 0||command.expectedHostInstance===context.instanceId;return command.durableSessionId!==session.sessionId||!instanceMatches?refusal(id,"attach_session","session_identity_mismatch",{reason:"durableSessionId or expectedHostInstance does not name this terminal's session",action:"read list_sessions and get_protocol_info again, then attach with their ids"}):command.takeover===!0?refusal(id,"attach_session","takeover_unsupported",{reason:"only a headless engine with a takeover ledger takes sessions over",action:"attach without takeover"}):(attachments.run(connection,()=>{attachments.attach(connection),attachments.writer.enqueue(attachments.handle,{id:command.id,type:"response",command:"attach_session",success:!0,data:{sessionId:attachments.handle,attached:!0}})}),null)}function releaseAttachment(context,connection,command){return command.sessionId!==context.attachments.handle||!context.attachments.detach(connection)?refusal(idOf(command),"release_attachment","session_not_attached",{reason:"this connection is not attached to that session",action:"attach first with attach_session or open_session"}):success(command.id,"release_attachment",{deferred:!1})}async function abort(context,connection,command){let key=keyOf(connection);if(command.intent==="stop"&&!context.attachments.roles.has(key,INTERACTIVE_USER_CAPABILITY))return failure(command.id,"abort",STOP_REQUIRES_INTERACTIVE_CLIENT);let{session}=context;return runAbort({session,by:{connectionId:key},sessionId:context.attachments.handle},command,()=>(command.intent==="stop"?session.stop():session.abort()).catch(cause=>{connection.send({type:"rpc_error",error:String(cause)})}))}function subscribe(context,connection,command){let{writer}=context.attachments,key=keyOf(connection),{answer:answer2,activate}=answerSubscribeSessions(command,key,writer.status);return activate===void 0?answer2:(writer.enqueueControl(answer2,key).then(()=>{writer.hasConnection(key)?writer.status.activate(activate):writer.status.unsubscribe(key,activate)}).catch(()=>writer.status.unsubscribe(key,activate)),null)}function samePath(requested,own){if(own===void 0)return!1;let canonical=path=>{try{return realpathSync(resolve(path))}catch{return resolve(path)}};return canonical(requested)===canonical(own)}var SESSION_CONTROL_COMMANDS=["get_available_models","get_available_thinking_levels","set_model","set_thinking_level","interrupt"];async function runSessionControlCommand(session,controls,command){let{id,type}=command;switch(type){case"get_available_models":return success(id,type,await availableModelsData(session));case"get_available_thinking_levels":return success(id,type,{levels:session.getAvailableThinkingLevels()});case"set_model":return await setModel(session,controls,command);case"set_thinking_level":{let level=session.getAvailableThinkingLevels().find(candidate=>candidate===command.level);return level===void 0?failure(id,type,unsupportedThinkingLevel(command.level)):(controls.selectThinkingLevel(level,command.scope!=="turn"),success(id,type))}case"interrupt":return success(id,type,await interruptRunningTurn(session,command.turnId,()=>controls.interruptTurn()));default:return}}async function setModel(session,controls,command){let{id,type}=command,model=(await session.modelRegistry.getAvailable()).find(candidate=>candidate.provider===command.provider&&candidate.id===command.modelId);if(model===void 0)return failure(id,type,`Model not found: ${String(command.provider)}/${String(command.modelId)}`);try{let change=await controls.selectModel(model);return success(id,type,{...model,systemPromptName:change?.systemPromptName})}catch(error){return failure(id,type,error instanceof Error?error.message:String(error))}}var TUI_CONTROL_CAPABILITY="tui_control",TUI_CONTROL_COMMANDS=["get_protocol_info","list_sessions","get_state","get_messages","get_entries","set_session_name","subscribe","wake","extension_ui_response",...SESSION_CONTROL_COMMANDS,...TUI_RPC_COMMANDS];async function runControlCommand(context,connection,command){let{id,type}=command,{session}=context;switch(type){case"get_protocol_info":{let build=engineBuildIdentity();return success(id,type,{protocolVersion:1,serverVersion:VERSION,capabilities:[TUI_CONTROL_CAPABILITY,SESSION_STATUS_CAPABILITY,TUI_OBSERVER_CAPABILITY,OBSERVE_READS_CAPABILITY,ENTRIES_TAIL_CAPABILITY,"snapshot_cursor","ui_answer_ledger"],mode:"tui",endpoint_kind:"tui",instanceId:context.instanceId,generation:0,engineVersion:build.text,engineOrdinal:build.ordinal,commands:TUI_CONTROL_COMMANDS})}case"list_sessions":return success(id,type,{sessions:[sessionRow(session)]});case"get_state":return success(id,type,{...buildRpcSessionState(session),turn_epoch:session.externalAdmission.turnEpoch,blocking_question:context.surface.blockingQuestion(),compacting:session.isCompacting,editor_has_draft:context.surface.draftHold()!==void 0,state_version:context.feed.stateVersion});case"get_messages":return success(id,type,{messages:session.messages});case"get_entries":return answerGetEntries(session,command);case"set_session_name":{let name=typeof command.name=="string"?command.name.trim():"";return name===""?failure(id,type,"Session name cannot be empty"):(session.setSessionName(name),success(id,type))}case"subscribe":{let cursor=typeof command.cursor=="number"?command.cursor:void 0;return success(id,type,{cursor:context.feed.subscribe(connection.id,cursor,connection.send)})}case"wake":{let ids=Array.isArray(command.delivery_ids)?command.delivery_ids.filter(entry=>typeof entry=="string"):void 0,result=await context.wake(ids);return success(id,type,{admitted:result.admitted??[]})}case"extension_ui_response":return answerQuestion(context,command);default:{let rpc=await runTuiRpcCommand(context,connection,command);return rpc!==void 0?rpc:await runSessionControlCommand(session,context.surface,command)??failure(id,type,"unsupported")}}}function sessionRow(session){let manager=session.sessionManager,created=manager.getHeader()?.timestamp??null,last=manager.getEntries().at(-1);return{sessionId:session.sessionId,sessionPath:session.sessionFile??null,session_file:session.sessionFile??null,kind:"interactive",surface:"tui",cwd:manager.getCwd(),name:session.sessionName??null,created_at:created,updated_at:last?.timestamp??created,attachments:1,retain_on_disconnect:!0}}function answerQuestion(context,command){if(command.secret===!0)return failure(command.id,"extension_ui_response","invalid_secret_response");let{surface,session}=context,ledger=sessionUiAnswerLedger(session),answerId=command.answerId;if(answerId!==void 0&&(typeof answerId!="string"||answerId.length<1||answerId.length>256))return failure(command.id,"extension_ui_response","invalid_answer_id");let frameId=command.id,requestId=answeredUiRequestId(command),receipt=requestId===void 0||answerId===void 0?void 0:ledger.lookup(requestId,answerId);if(receipt==="replay")return{...success(frameId,"extension_ui_response"),disposition:receipt};if(receipt==="already_answered")return{...failure(frameId,"extension_ui_response","already_answered"),disposition:receipt};let request=requestId===void 0?void 0:surface.pendingQuestion(requestId);if(requestId===void 0||request===void 0)return{...failure(frameId,"extension_ui_response",answerId===void 0?"unknown_request":"unknown_extension_ui_request"),disposition:"unknown"};let response=questionResponse(command,request.questions);return response==="question_incomplete"?failure(frameId,"extension_ui_response",response):response===void 0||!surface.answerQuestion(requestId,response)?failure(frameId,"extension_ui_response","invalid_response"):(ledger.record(requestId,answerId),{...success(frameId,"extension_ui_response"),disposition:"applied"})}function questionResponse(command,questions){if(command.cancelled===!0)return{status:"cancelled",cancelOrigin:"user",answers:{},unanswered:[]};let answers=command.answers;if(typeof answers!="object"||answers===null||Array.isArray(answers))return;let parsed={};for(let[question,value]of Object.entries(answers)){if(typeof value!="object"||value===null||!("selected"in value)||!Array.isArray(value.selected))return;let selected=value.selected.filter(entry=>typeof entry=="string"),text="text"in value&&typeof value.text=="string"?value.text:void 0;parsed[question]=text===void 0?{selected}:{selected,text}}let comment=typeof command.comment=="string"?command.comment:void 0,status=settledQuestionStatus(parsed,comment);if(status===void 0)return"question_incomplete";let unanswered=unansweredQuestionIds(questions,parsed);return{status,resolvedBy:"control_endpoint",answers:parsed,unanswered,...comment===void 0?{}:{comment}}}var ControlFeed=class{constructor(){this.seq=0,this.stateVersionValue=0,this.ring=[],this.subscribers=new Map}get stateVersion(){return this.stateVersionValue}publish(kind,data={}){kind==="state"&&(this.stateVersionValue+=1);let event={type:"session_control_event",seq:++this.seq,kind,data:kind==="state"?{...data,state_version:this.stateVersionValue}:data};this.ring.push(event),this.ring.length>256&&this.ring.shift();for(let send of this.subscribers.values())send(event)}subscribe(connection,cursor,send){if(this.subscribers.set(connection,send),cursor!==void 0)for(let event of this.ring)event.seq>cursor&&send(event);return this.seq}unsubscribe(connection){this.subscribers.delete(connection)}clear(){this.subscribers.clear()}};import{createHash}from"node:crypto";import{rmSync}from"node:fs";import{chmod as chmod2,lstat,mkdir,realpath,rm as rm2}from"node:fs/promises";import{tmpdir}from"node:os";import{dirname,join}from"node:path";var PRIVATE_DIRECTORY_MODE=448,REGISTRY_LOCK_WAIT_MS=5e3;async function resolveTuiSocket(agentDir,instanceId){let name=tuiSocketName(instanceId),primary=join(agentDir,"rpc","tui",name);if(process.platform==="win32"||Buffer.byteLength(primary)<=MAX_SOCKET_PATH_BYTES)return await makePrivateDirectory(dirname(primary)),primary;let fallbackName=`senpi-rpc-${createHash("sha256").update(agentDir).digest("hex").slice(0,8)}`,scopedRoot=join(tmpdir(),fallbackName),root=Buffer.byteLength(join(scopedRoot,"tui",name))<=MAX_SOCKET_PATH_BYTES?scopedRoot:join("/tmp",fallbackName),tuiRoot=join(root,"tui");await makePrivateDirectory(tuiRoot);for(let directory of[root,tuiRoot])await assertPrivateDirectory(directory);let fallback=join(tuiRoot,name);if(Buffer.byteLength(fallback)>MAX_SOCKET_PATH_BYTES)throw new Error(`socket path ${fallback} exceeds ${MAX_SOCKET_PATH_BYTES} bytes`);return fallback}async function makePrivateDirectory(directory){await mkdir(directory,{recursive:!0,mode:PRIVATE_DIRECTORY_MODE}),await chmod2(directory,PRIVATE_DIRECTORY_MODE)}async function assertPrivateDirectory(directory){let stat=await lstat(await realpath(directory)),ownedByUs=process.getuid===void 0||stat.uid===process.getuid();if(!stat.isDirectory()||!ownedByUs||(stat.mode&63)!==0)throw new Error(`alt_root_unsafe: ${directory} is not a private directory of this user`)}async function registerTuiEndpoint(options){let paths=createHostDaemonPaths({socket:options.socket,agentDir:options.agentDir}),release=await acquireHostEnsureLock(options.socket,REGISTRY_LOCK_WAIT_MS);try{await writeHostRegistration(paths,{record:{pid:process.pid,processStartTime:await thisProcessStartTime()},socket:options.socket,instanceId:options.instanceId,generation:0,launchProfileId:"tui",build:engineBuildIdentity()},{fresh:!0}),await createDaemonDirectories(paths,{kind:"tui"})}finally{await release()}return{paths,socket:options.socket}}async function unregisterTuiEndpoint(entry){let release=await acquireHostEnsureLock(entry.socket,REGISTRY_LOCK_WAIT_MS).catch(()=>{});try{await rm2(entry.socket,{force:!0}),await rm2(socketSecretPath(entry.socket),{force:!0}),await rm2(entry.paths.dir,{recursive:!0,force:!0})}finally{await release?.()}}function unregisterTuiEndpointSync(entry){rmSync(entry.socket,{force:!0}),rmSync(socketSecretPath(entry.socket),{force:!0}),rmSync(entry.paths.dir,{recursive:!0,force:!0})}var REPORT_TEXT_LIMIT=4e3;function activateControlEndpoint(parts){let{context,feed,wake}=parts,{session,surface}=context;session.externalAdmission.setEditorSource(()=>editorHold(context));let unsubscribeSession=session.subscribe(event=>{event.type==="agent_start"?feed.publish("state"):event.type==="agent_idle"?(feed.publish("completion",{turn_epoch:session.externalAdmission.turnEpoch}),feed.publish("state"),wake("idle")):event.type==="agent_end"||event.type==="queue_update"||event.type==="compaction_start"||event.type==="compaction_end"?feed.publish("state"):event.type==="message_end"&&event.message.role==="assistant"&&feed.publish("report",{text:assistantText(event.message).slice(0,REPORT_TEXT_LIMIT)})}),unsubscribeEmitted=session.externalAdmission.onEmitted(()=>{wake("emitted")}),unsubscribeInputs=session.externalAdmission.onInputsSettled(()=>queueMicrotask(()=>{wake("submission")})),stopContinue=onProcessContinue(()=>{wake("continue")}),exitCleanup=()=>unregisterTuiEndpointSync(parts.entry);process.once("exit",exitCleanup);let disposing,dispose=()=>(disposing??=(async()=>{process.removeListener("exit",exitCleanup),unsubscribeSession(),unsubscribeEmitted(),unsubscribeInputs(),stopContinue(),parts.stopInbox(),parts.scheduler.dispose(),feed.clear(),session.externalAdmission.setEditorSource(void 0),await parts.server.close(),await sessionReferenced(parts.options,surface)||await session.sessionManager.discardHeaderOnlyFile(),await unregisterTuiEndpoint(parts.entry)})(),disposing);return{socket:parts.entry.socket,wake:reason=>{wake(reason)},noteState:()=>feed.publish("state"),noteQuestions:()=>feed.publish("question",{pending:[...surface.pendingQuestionIds()]}),dispose}}function editorHold(context){let hold=context.surface.draftHold(),revision=context.editorRevision();return hold===void 0?{revision}:{hold_reason:hold,revision}}async function sessionReferenced(options,surface){if(options.isSessionReferenced===void 0)return!1;try{return await options.isSessionReferenced()}catch(error){return surface.notice(`control endpoint kept the session file: ${error instanceof Error?error.message:String(error)}`),!0}}function assistantText(message){return message.role!=="assistant"?"":message.content.flatMap(part=>part.type==="text"?[part.text]:[]).join(`
|
|
3
|
-
`)}async function registerSessionControlEndpoint(context,options){let cleanups=[];try{let endpoint=await openEndpoint(context,options,cleanups);return{registration:{status:"registered",socket:endpoint.socket,dispose:endpoint.dispose},endpoint}}catch(error){for(let cleanup of cleanups.reverse())await Promise.resolve(cleanup()).catch(()=>{});let reason=errorText(error);return context.surface.notice(`control endpoint unavailable: ${reason}`),{registration:{status:"failed",reason}}}}async function openEndpoint(context,options,cleanups){let{session,agentDir,surface}=context;thisProcessStartTime();let headerDurable=session.sessionManager.persistHeaderNow();headerDurable.catch(()=>{});let instanceId=randomUUID2(),socket=await resolveTuiSocket(agentDir,instanceId);cleanups.push(()=>rm3(socket,{force:!0}),()=>rm3(socketSecretPath(socket),{force:!0}));let secret=await createSocketSecret(socketSecretPath(socket)),feed=new ControlFeed,failing,scheduler=new WakeScheduler(async event=>{await session.extensionRunner.emit(event);let result=await options.drain(event)??{admitted:[]};return failing=void 0,result},error=>{let reason=errorText(error);reason!==failing&&(failing=reason,surface.notice(`control endpoint drain failed: ${reason}`))});cleanups.push(()=>scheduler.dispose());let wake=(reason,ids)=>scheduler.request(reason,ids),presenters=context.mcpPresenter?await startTerminalPresenters(context.mcpPresenter):void 0;presenters&&cleanups.push(presenters.dispose);let attachments=new ControlAttachments(session,()=>surface.pendingQuestionIds().length,presenters?.surface);cleanups.push(()=>attachments.dispose());let commandContext={instanceId,session,surface,feed,wake:ids=>wake("command",ids),attachments},server=await listenControlSocket(socket,secret,{command:(connection,command)=>runControlCommand(commandContext,connection,command),opened:connection=>attachments.opened(connection),respond:(connection,answer2)=>attachments.respond(connection,answer2),closed:connection=>{feed.unsubscribe(connection.id),attachments.closed(connection)},listenerError:error=>surface.notice(`control endpoint listener failed: ${error.message}`)});cleanups.push(()=>server.close()),await headerDurable;let entry=await registerTuiEndpoint({agentDir,socket,instanceId});cleanups.push(()=>unregisterTuiEndpoint(entry));let inbox=await watchInbox(options.inboxDir,()=>{wake("inbox")},error=>surface.notice(`control endpoint inbox watch failed: ${errorText(error)}`));cleanups.push(()=>inbox.stop());let endpoint=activateControlEndpoint({context,options,entry,server,scheduler,feed,stopInbox:inbox.stop,wake});return endpoint.wake("inbox"),inbox.armed.then(()=>endpoint.wake("inbox")),setImmediate(()=>{gcHostEndpoints(agentDir,{kinds:["tui"]}).catch(error=>surface.notice(`control endpoint gc of dead terminals failed: ${errorText(error)}`))}),presenters?{...endpoint,dispose:()=>endpoint.dispose().finally(presenters.dispose)}:endpoint}function errorText(error){return error instanceof Error?error.message:String(error)}export{registerSessionControlEndpoint};
|