pi-orchestrator-config 4.3.11 → 4.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -66
- package/extensions/coms/coms-p2p.ts +53 -16
- package/extensions/orchestrator/async-agents.ts +127 -42
- package/extensions/orchestrator/async-result-format.ts +58 -0
- package/extensions/orchestrator/async-runner.ts +20 -4
- package/extensions/orchestrator/cron-status-format.ts +6 -6
- package/extensions/orchestrator/cron-status-ui.ts +3 -3
- package/extensions/orchestrator/cron-store.ts +208 -0
- package/extensions/orchestrator/cron.ts +168 -699
- package/extensions/orchestrator/extended-autocomplete.ts +27 -27
- package/extensions/orchestrator/reviewer-output-archive.ts +69 -0
- package/extensions/orchestrator/status.ts +7 -2
- package/extensions/pidash/pidash-ui/dist/assets/{index-BoNVhD5K.js → index-Ehfwjr9d.js} +1 -1
- package/extensions/pidash/pidash-ui/dist/index.html +1 -1
- package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +3 -7
- package/extensions/pidash/pidash-ui/src/components/MessageList.tsx +0 -4
- package/extensions/pidash/pidash-ui/tsconfig.json +1 -0
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/extensions/pitasks/index.ts +2 -5
- package/extensions/pitasks/task-tools.ts +2 -2
- package/package.json +2 -1
- package/scripts/build-extension-uis.sh +6 -6
package/README.md
CHANGED
|
@@ -10,36 +10,36 @@ A [pi package](https://github.com/badlogic/pi-mono) that implements an **orchest
|
|
|
10
10
|
|
|
11
11
|
Single extension that provides:
|
|
12
12
|
|
|
13
|
-
| Feature
|
|
14
|
-
|
|
|
15
|
-
| **Subagent tool**
|
|
16
|
-
| **`list_models`**
|
|
17
|
-
| **Async background agents**
|
|
18
|
-
| **CLI / ACPX providers**
|
|
19
|
-
| **`/btw` command**
|
|
20
|
-
| **`/async-status` command**
|
|
21
|
-
| **`/async-kill` command**
|
|
22
|
-
| **`ask_user` tool**
|
|
23
|
-
| **Python/pip enforcement**
|
|
24
|
-
| **Git protection**
|
|
25
|
-
| **Remote script exec block**
|
|
26
|
-
| **Dangerous command gate**
|
|
27
|
-
| **Rule injection**
|
|
28
|
-
| **Git status**
|
|
29
|
-
| **Desktop notifications**
|
|
30
|
-
| **File preview**
|
|
31
|
-
| **Pidash dashboard**
|
|
32
|
-
| **Pidiff viewer**
|
|
33
|
-
| **Dreaming**
|
|
34
|
-
| **Memory enforcement**
|
|
35
|
-
| **Upgrade changelog**
|
|
36
|
-
| **Task tracking**
|
|
37
|
-
| **Neovim integration**
|
|
38
|
-
| **Inter-agent communication**
|
|
39
|
-
| **Slash commands**
|
|
40
|
-
| **GitHub autocomplete**
|
|
41
|
-
| **Command arg completions**
|
|
42
|
-
| **Discord bot**
|
|
13
|
+
| Feature | Description |
|
|
14
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| **Subagent tool** | Delegate tasks to specialist agents (single, parallel, chain, async modes); optional `model` param for model override |
|
|
16
|
+
| **`list_models`** | List available models and providers for subagent model override |
|
|
17
|
+
| **Async background agents** | Spawn agents in background with `async: true` — results surface automatically when complete. Fullscreen overlay dashboard with live output, keyboard nav, and kill support (`/async-status`, `/async-kill`). On **acpx** parents, optional async is coerced to sync; dream/cron need `internal_operations_provider` + `internal_operations_model`. `cli-*` providers support async natively (see `dev-docs/cli-provider.md`) |
|
|
18
|
+
| **CLI / ACPX providers** | Optional `cli_agents` / `acpx_agents` register `cli-*` / `acpx-*` via native `createProvider` (pi ≥ 0.84): `/login cli-<agent>` or `/login acpx-<agent>`, model refresh, filter when unavailable. Missing context window / maxTokens / cost filled from models.dev (`~/.pi/pi-config/models.dev.json`, 1-day refresh). Cold-start default restore (#753): gates `startup`\|`new`, empty `enabledModels`, trusted project merge — see `dev-docs/cli-provider.md`. Headless Cursor `--approve-mcps` when `CLI_APPROVE_MCPS` is set (`false` opts out) or the process is a sidecar (`SIDECAR_PORT`; `startSidecar()` stamps it while running and restores it on `close()`). Executable sidecar consumers should call `bindSidecarListenExit()` so bind/`stopped` fatals exit 1. Headless Gemini defaults `GEMINI_CLI_TRUST_WORKSPACE=true` and preserves an explicit parent value (including `false`). |
|
|
19
|
+
| **`/btw` command** | Quick side questions without polluting conversation history — ephemeral overlay |
|
|
20
|
+
| **`/async-status` command** | Show status of background agents — select one for live output streaming |
|
|
21
|
+
| **`/async-kill` command** | Kill async agents (overlay picker or by name/id) |
|
|
22
|
+
| **`ask_user` tool** | Structured user input with options and free-text — used by workflows |
|
|
23
|
+
| **Python/pip enforcement** | Auto-fixes `python`/`python3` → `uv run python`; blocks `pip`/`pip3` — requires `uv` |
|
|
24
|
+
| **Git protection** | Blocks commits/pushes to main/master, merged branches, `--no-verify`, `git add .` |
|
|
25
|
+
| **Remote script exec block** | Blocks `curl \| bash`, `eval $(curl)`, etc. Allows safe `VAR=$(curl ...)` variable assignments |
|
|
26
|
+
| **Dangerous command gate** | Confirms `rm -rf`, `sudo`, `mkfs`, etc. |
|
|
27
|
+
| **Rule injection** | Injects orchestrator routing rules into system prompt |
|
|
28
|
+
| **Git status** | Live git status in status line with colored icons — updates after every tool call. Shows clickable `#N` when the branch has an open PR. Last-activity clock `⏱ HH:MM (Xm/Xh ago)` shows time since last response |
|
|
29
|
+
| **Desktop notifications** | Notifies via `notify-send` on task completion, waiting for input, and action required |
|
|
30
|
+
| **File preview** | Serves generated HTML/frontend files via HTTP for browser preview from container |
|
|
31
|
+
| **Pidash dashboard** | Live web dashboard — multi-session monitoring, browser messaging, model switching, live session name updates, reasoning token display |
|
|
32
|
+
| **Pidiff viewer** | Per-project diff viewer with review comments — branch diffs, file tree, inline comments, git-based ignore rules |
|
|
33
|
+
| **Dreaming** | Background memory consolidation — extracts memories from sessions, deduplicates, maintains topic-based memory |
|
|
34
|
+
| **Memory enforcement** | Code-enforced memory entries — triggers on bash/tool/file events, actions: block, run_after, warn. LLM cannot ignore enforced rules. Dreaming-safe via _(enforced)_ marker |
|
|
35
|
+
| **Upgrade changelog** | Shows release notes on session start after pi-config version upgrade |
|
|
36
|
+
| **Task tracking** | Structured task lists for multi-step workflows — live widget, progress tracking, reminder nudges (owned, based on [@tintinweb/pi-tasks](https://github.com/tintinweb/pi-tasks) MIT) |
|
|
37
|
+
| **Neovim integration** | Send changed files and review findings to nvim's quickfix list — only active when running inside nvim |
|
|
38
|
+
| **Inter-agent communication** | P2P (`/coms`) agent communication — on-demand activation via slash command |
|
|
39
|
+
| **Slash commands** | `/pr-review`, `/issue-review`, `/release`, `/review-local`, `/review-status`, `/query-db`, `/btw`, `/async-status`, `/async-kill`, `/status`, `/dream`, `/remember`, `/coms`, `/pi-config-settings`, `/mcpc connect` — with autocomplete argument hints |
|
|
40
|
+
| **GitHub autocomplete** | Type `#` in the editor to get issue/PR suggestions from the current repo — lazy-loaded, 5min cache |
|
|
41
|
+
| **Command arg completions** | Tab-complete arguments for slash commands — providers and models for `/external-ai`, branches for `/review-local`, PR numbers for `/pr-review`, and more |
|
|
42
|
+
| **Discord bot** | Control pi sessions from your phone via Discord DMs — send prompts, answer ask_user dialogs, switch sessions |
|
|
43
43
|
|
|
44
44
|
### Agents (26)
|
|
45
45
|
|
|
@@ -55,34 +55,34 @@ Single extension that provides:
|
|
|
55
55
|
|
|
56
56
|
### Prompt Templates
|
|
57
57
|
|
|
58
|
-
| Prompt | Description
|
|
59
|
-
| ----------------------------------------------------------------------------------------------- |
|
|
60
|
-
| `/implement <task>` | scout → planner → worker
|
|
61
|
-
| `/scout-and-plan <task>` | scout → planner
|
|
62
|
-
| `/implement-and-review <task>` | worker → 5 reviewers → worker
|
|
63
|
-
| `/pr-review [number\|url]` | Fetch PR diff, check past review comments, review with guidelines, post and track comments
|
|
64
|
-
| `/issue-review` | Review a GitHub issue spec and fix it
|
|
65
|
-
| `/release [flags]` | Create GitHub release with changelog and version bumping
|
|
66
|
-
| `/review-local [branch]` | Review local uncommitted or branch changes
|
|
67
|
-
| `/review-handler [url] [--autorabbit] [--autoqodo]` | Process PR review comments, fix approved items
|
|
68
|
-
| `/domain-model [focus area]` | Scan codebase and build/update a CONTEXT.md domain glossary for consistent AI vocabulary
|
|
69
|
-
| `/refine-review <url>` | Refine and improve existing PR review comments
|
|
70
|
-
| `/coderabbit-rate-limit [number\|url]` | Handle CodeRabbit rate limiting on PRs
|
|
71
|
-
| `/create-coms-feature-manager` | Generate a coms feature-manager prompt customized for the current project (source template: `templates/coms-feature-manager-prompt.md`)
|
|
72
|
-
| `/query-db <command>` | Query the review comments database
|
|
73
|
-
| `/external-ai <agent> [--model <model>] [--session-id <id>] [--fix\|--peer\|--resume] <prompt>` | Run prompts via AI CLIs directly (cursor, claude, gemini) — full model access
|
|
74
|
-
| `/external-ai-models-refresh` | Refresh cached AI CLI models for autocomplete
|
|
75
|
-
| `/dream` | Run memory consolidation — extract, deduplicate, maintain topic-based memory
|
|
76
|
-
| `/remember <what>` | Save a memory for future sessions
|
|
77
|
-
| `/dream-auto` | Toggle automatic memory dreaming (every 3h + session end)
|
|
78
|
-
| `/cron add\|list\|list-all\|remove`
|
|
79
|
-
| `/async-kill [name\|id\|all]` | Kill async agents (overlay picker or by name/id)
|
|
80
|
-
| `/pi-config-settings [project\|global]` | Interactive TUI settings editor — browse all pi-config settings with scope indicators, smart pickers for providers/models, and immediate save
|
|
81
|
-
| `/status` | Unified session snapshot — async agents, cron tasks, git branch, context usage
|
|
82
|
-
| `/nvim-changed-files` | Send git changed files to nvim's quickfix list (only inside nvim)
|
|
83
|
-
| `/pidiff start\|stop\|restart\|status` | Manage the pidiff diff viewer server (per-project)
|
|
84
|
-
| `/coms start\|stop\|status` | P2P local agent communication (Unix socket)
|
|
85
|
-
| `/mcpc connect` | Connect MCP servers from `~/.pi/pi-config/mcp.json` (`mcpc connect --stdio`). Run after editing that file.
|
|
58
|
+
| Prompt | Description |
|
|
59
|
+
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `/implement <task>` | scout → planner → worker |
|
|
61
|
+
| `/scout-and-plan <task>` | scout → planner |
|
|
62
|
+
| `/implement-and-review <task>` | worker → 5 reviewers → worker |
|
|
63
|
+
| `/pr-review [number\|url]` | Fetch PR diff, check past review comments, review with guidelines, post and track comments |
|
|
64
|
+
| `/issue-review` | Review a GitHub issue spec and fix it |
|
|
65
|
+
| `/release [flags]` | Create GitHub release with changelog and version bumping |
|
|
66
|
+
| `/review-local [branch]` | Review local uncommitted or branch changes |
|
|
67
|
+
| `/review-handler [url] [--autorabbit] [--autoqodo]` | Process PR review comments, fix approved items |
|
|
68
|
+
| `/domain-model [focus area]` | Scan codebase and build/update a CONTEXT.md domain glossary for consistent AI vocabulary |
|
|
69
|
+
| `/refine-review <url>` | Refine and improve existing PR review comments |
|
|
70
|
+
| `/coderabbit-rate-limit [number\|url]` | Handle CodeRabbit rate limiting on PRs |
|
|
71
|
+
| `/create-coms-feature-manager` | Generate a coms feature-manager prompt customized for the current project (source template: `templates/coms-feature-manager-prompt.md`) |
|
|
72
|
+
| `/query-db <command>` | Query the review comments database |
|
|
73
|
+
| `/external-ai <agent> [--model <model>] [--session-id <id>] [--fix\|--peer\|--resume] <prompt>` | Run prompts via AI CLIs directly (cursor, claude, gemini) — full model access |
|
|
74
|
+
| `/external-ai-models-refresh` | Refresh cached AI CLI models for autocomplete |
|
|
75
|
+
| `/dream` | Run memory consolidation — extract, deduplicate, maintain topic-based memory |
|
|
76
|
+
| `/remember <what>` | Save a memory for future sessions |
|
|
77
|
+
| `/dream-auto` | Toggle automatic memory dreaming (every 3h + session end) |
|
|
78
|
+
| `/cron add\|list\|list-all\|remove [--persist]` | Schedule recurring tasks. Session jobs end with the session; `--persist` keeps a job across Pi sessions in this project. Add/remove mutations still work when leader election fails closed on an unsupported platform. For example: `/cron add every 2h check for new issues --persist` or `/cron remove persist:<uuid>`. `/cron list` opens an overlay for the selected jobs (or both when omitted). Persistent delivery is local at-least-once: a crash or leader recovery can repeat a run. |
|
|
79
|
+
| `/async-kill [name\|id\|all]` | Kill async agents (overlay picker or by name/id) |
|
|
80
|
+
| `/pi-config-settings [project\|global]` | Interactive TUI settings editor — browse all pi-config settings with scope indicators, smart pickers for providers/models, and immediate save |
|
|
81
|
+
| `/status` | Unified session snapshot — async agents, cron tasks, git branch, context usage |
|
|
82
|
+
| `/nvim-changed-files` | Send git changed files to nvim's quickfix list (only inside nvim) |
|
|
83
|
+
| `/pidiff start\|stop\|restart\|status` | Manage the pidiff diff viewer server (per-project) |
|
|
84
|
+
| `/coms start\|stop\|status` | P2P local agent communication (Unix socket) |
|
|
85
|
+
| `/mcpc connect` | Connect MCP servers from `~/.pi/pi-config/mcp.json` (`mcpc connect --stdio`). Run after editing that file. |
|
|
86
86
|
|
|
87
87
|
### Inter-Agent Communication (coms)
|
|
88
88
|
|
|
@@ -98,14 +98,14 @@ Two systems for Pi agents to communicate with each other, activated on-demand vi
|
|
|
98
98
|
|
|
99
99
|
**Tools available once activated:**
|
|
100
100
|
|
|
101
|
-
| Tool
|
|
102
|
-
|
|
|
103
|
-
| `*_list`
|
|
104
|
-
| `*_send`
|
|
105
|
-
| `*_get`
|
|
106
|
-
| `*_await`
|
|
107
|
-
| `coms_queue_inspect` | Preview owned queued P2P messages before recovery
|
|
108
|
-
| `coms_queue_clear`
|
|
101
|
+
| Tool | Description |
|
|
102
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
103
|
+
| `*_list` | List peer agents with names, models, context usage, queue depth |
|
|
104
|
+
| `*_send` | Send a prompt to a peer agent |
|
|
105
|
+
| `*_get` | Non-blocking poll for a response |
|
|
106
|
+
| `*_await` | Block until response arrives |
|
|
107
|
+
| `coms_queue_inspect` | Preview owned queued P2P messages before recovery |
|
|
108
|
+
| `coms_queue_clear` | Irreversibly clear only an inspect preview |
|
|
109
109
|
|
|
110
110
|
Queue recovery is explicit. Run `coms_queue_inspect` first and review its
|
|
111
111
|
body-free preview. Then pass its `preview_id` to `coms_queue_clear`.
|
|
@@ -154,13 +154,15 @@ interface RegistryEntry {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
interface PendingReply {
|
|
157
|
-
resolve
|
|
158
|
-
reject
|
|
159
|
-
timer
|
|
160
|
-
promise
|
|
157
|
+
resolve?: (value: any) => void;
|
|
158
|
+
reject?: (err: Error) => void;
|
|
159
|
+
timer?: NodeJS.Timeout | null;
|
|
160
|
+
promise?: Promise<{ response?: any; error?: string | null; queued_msg_ids?: string[] }>;
|
|
161
161
|
result?: { response?: any; error?: string | null; queued_msg_ids?: string[] };
|
|
162
|
+
target_session?: string;
|
|
162
163
|
target_name?: string;
|
|
163
|
-
|
|
164
|
+
sent_at?: number;
|
|
165
|
+
created_at?: string;
|
|
164
166
|
/** Creation time drives body-free recovery previews. */
|
|
165
167
|
queued_at?: string;
|
|
166
168
|
}
|
|
@@ -549,7 +551,10 @@ function sendEnvelope(endpoint: string, envelope: Envelope | Pong | { type: stri
|
|
|
549
551
|
sock.write(JSON.stringify(envelope) + "\n");
|
|
550
552
|
const line = await readOneLine(sock);
|
|
551
553
|
const parsed = JSON.parse(line);
|
|
552
|
-
|
|
554
|
+
// The response is complete. Destroy the outbound client now instead of
|
|
555
|
+
// waiting for the peer's half-close, which can outlive a test session.
|
|
556
|
+
try { sock.destroy(); } catch { /* ignore */ }
|
|
557
|
+
log.debug("coms_envelope_sent", { type: envelope.type, has_msg_id: !!envelope.msg_id });
|
|
553
558
|
if (settled) return;
|
|
554
559
|
settled = true;
|
|
555
560
|
if (parsed && parsed.type === "nack") {
|
|
@@ -642,6 +647,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
642
647
|
log.debug("recovery_preview_retained", { session: targetSession, count: itemIds.length, active: queueRecoveryPreviews.size });
|
|
643
648
|
}
|
|
644
649
|
let server: net.Server | null = null;
|
|
650
|
+
const connections = new Set<net.Socket>();
|
|
645
651
|
let fsWatcher: fs.FSWatcher | null = null;
|
|
646
652
|
let pingWorker: Worker | null = null;
|
|
647
653
|
let pingWorkerReady = false;
|
|
@@ -795,7 +801,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
795
801
|
const queuedMsgIds: string[] = Array.isArray(env.queued_msg_ids) ? env.queued_msg_ids.filter((id: unknown) => typeof id === "string") : [];
|
|
796
802
|
pending.result = { response: env.response, error: env.error ?? null, queued_msg_ids: queuedMsgIds };
|
|
797
803
|
try {
|
|
798
|
-
pending.resolve(pending.result);
|
|
804
|
+
pending.resolve?.(pending.result);
|
|
799
805
|
} catch (e: any) {
|
|
800
806
|
log.error("resolve_failed", env.msg_id, e?.message);
|
|
801
807
|
}
|
|
@@ -892,6 +898,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
892
898
|
}
|
|
893
899
|
|
|
894
900
|
function connHandler(socket: net.Socket): void {
|
|
901
|
+
connections.add(socket);
|
|
902
|
+
socket.once("close", () => connections.delete(socket));
|
|
895
903
|
let buf = "";
|
|
896
904
|
let handled = false;
|
|
897
905
|
const onData = (chunk: Buffer) => {
|
|
@@ -1030,7 +1038,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1030
1038
|
const storedPreview = queueRecoveryPreviews.get(env.preview_id);
|
|
1031
1039
|
if (!storedPreview || storedPreview.targetSession !== env.sender_session) {
|
|
1032
1040
|
error = "invalid_preview";
|
|
1033
|
-
try { socket.write(JSON.stringify({ type: "ack", msg_id: env.msg_id, affected, error }) + "\n"); } catch {}
|
|
1041
|
+
try { socket.write(JSON.stringify({ type: "ack", msg_id: env.msg_id, affected, error }) + "\n"); socket.end(); } catch {}
|
|
1034
1042
|
log.warn("recovery_clear_denied", { sender: env.sender_name, reason: error });
|
|
1035
1043
|
return;
|
|
1036
1044
|
}
|
|
@@ -1056,7 +1064,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1056
1064
|
}));
|
|
1057
1065
|
const preview = buildQueuePreview("local", items, Date.now(), ulid());
|
|
1058
1066
|
retainQueueRecoveryPreview(preview.previewId, env.sender_session, preview.items.map(item => item.id));
|
|
1059
|
-
try { socket.write(JSON.stringify({ type: "ack", msg_id: env.msg_id, affected: 0, error: null, preview }) + "\n"); } catch {}
|
|
1067
|
+
try { socket.write(JSON.stringify({ type: "ack", msg_id: env.msg_id, affected: 0, error: null, preview }) + "\n"); socket.end(); } catch {}
|
|
1060
1068
|
log.info("recovery_preview", { provider: "local", sender: env.sender_name, count: preview.items.length });
|
|
1061
1069
|
return;
|
|
1062
1070
|
} else if (action === "prioritize" && env.target_msg_id) {
|
|
@@ -1078,6 +1086,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1078
1086
|
// Send ack with result — includes error and affected count
|
|
1079
1087
|
try {
|
|
1080
1088
|
socket.write(JSON.stringify({ type: "ack", msg_id: env.msg_id, affected, error }) + "\n");
|
|
1089
|
+
socket.end();
|
|
1081
1090
|
} catch { /* ignore */ }
|
|
1082
1091
|
|
|
1083
1092
|
maybeRefreshWidget();
|
|
@@ -3217,11 +3226,22 @@ Do not respond to this message.`;
|
|
|
3217
3226
|
|
|
3218
3227
|
// ━━ Clean shutdown ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
3219
3228
|
let shuttingDown = false;
|
|
3229
|
+
async function stopPingWorker(worker: Worker): Promise<void> {
|
|
3230
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
3231
|
+
const exited = new Promise<boolean>((resolve) => worker.once("exit", () => resolve(true)));
|
|
3232
|
+
log.info("ping_worker_stop", { outcome: "shutdown_requested" });
|
|
3233
|
+
try { worker.postMessage({ type: "shutdown" }); } catch {}
|
|
3234
|
+
const graceful = await Promise.race([exited, new Promise<boolean>((resolve) => { timeout = setTimeout(() => resolve(false), 1_000); })]);
|
|
3235
|
+
if (timeout) clearTimeout(timeout);
|
|
3236
|
+
if (!graceful) try { await worker.terminate(); } catch {}
|
|
3237
|
+
log.info("ping_worker_stop", { outcome: graceful ? "graceful" : "terminated" });
|
|
3238
|
+
}
|
|
3220
3239
|
async function cleanShutdown(): Promise<void> {
|
|
3221
3240
|
if (shuttingDown) return;
|
|
3222
3241
|
shuttingDown = true;
|
|
3223
3242
|
try { pi.events.emit("pidash:coms-identity", { name: null, purpose: null }); } catch {}
|
|
3224
3243
|
const ident = identity; // snapshot before nulling
|
|
3244
|
+
const shutdownCtx = currentCtx;
|
|
3225
3245
|
identity = null; // null identity so old handlers from previous reload cycles exit via !identity check
|
|
3226
3246
|
if (fsWatcher) { try { fsWatcher.close(); } catch { /* ignore */ } fsWatcher = null; }
|
|
3227
3247
|
if (keepaliveTimer) { try { clearInterval(keepaliveTimer); } catch { /* ignore */ } keepaliveTimer = null; }
|
|
@@ -3257,13 +3277,20 @@ Do not respond to this message.`;
|
|
|
3257
3277
|
}
|
|
3258
3278
|
}
|
|
3259
3279
|
if (server) {
|
|
3260
|
-
|
|
3280
|
+
const activeServer = server;
|
|
3261
3281
|
server = null;
|
|
3282
|
+
try {
|
|
3283
|
+
for (const connection of connections) connection.destroy();
|
|
3284
|
+
connections.clear();
|
|
3285
|
+
activeServer.closeAllConnections?.(); activeServer.close(); activeServer.unref();
|
|
3286
|
+
} catch { /* ignore */ }
|
|
3262
3287
|
}
|
|
3263
3288
|
if (pingWorker) {
|
|
3264
|
-
|
|
3289
|
+
const worker = pingWorker;
|
|
3265
3290
|
pingWorker = null;
|
|
3266
3291
|
pingWorkerReady = false;
|
|
3292
|
+
await stopPingWorker(worker);
|
|
3293
|
+
log.info("ping_worker_shutdown", { outcome: "graceful_or_terminated" });
|
|
3267
3294
|
}
|
|
3268
3295
|
if (ident) {
|
|
3269
3296
|
if (process.platform !== "win32") {
|
|
@@ -3272,15 +3299,25 @@ Do not respond to this message.`;
|
|
|
3272
3299
|
try { removeRegistryEntry(ident.project, ident.coms_session_id); } catch { /* ignore */ }
|
|
3273
3300
|
log.info("shutdown", ident.coms_session_id);
|
|
3274
3301
|
}
|
|
3302
|
+
for (const pending of pendingReplies.values()) {
|
|
3303
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
3304
|
+
if (pending.resolve) pending.resolve({ error: "session_closed" });
|
|
3305
|
+
}
|
|
3306
|
+
pendingReplies.clear();
|
|
3307
|
+
pendingOutbound.clear();
|
|
3275
3308
|
queueRecoveryPreviews.clear();
|
|
3309
|
+
process.off("SIGINT", handleSigint);
|
|
3310
|
+
process.off("SIGTERM", handleSigterm);
|
|
3276
3311
|
log.debug("recovery_previews_cleared", { reason: "shutdown" });
|
|
3277
|
-
if (
|
|
3278
|
-
try {
|
|
3279
|
-
try {
|
|
3312
|
+
if (shutdownCtx?.hasUI) {
|
|
3313
|
+
try { shutdownCtx.ui.setWidget("coms-pool", undefined); } catch { /* ignore */ }
|
|
3314
|
+
try { shutdownCtx.ui.setStatus("coms", undefined); } catch { /* ignore */ }
|
|
3280
3315
|
}
|
|
3281
3316
|
}
|
|
3282
3317
|
|
|
3318
|
+
const handleSigint = () => { log.info("signal_received", { signal: "SIGINT" }); void cleanShutdown(); };
|
|
3319
|
+
const handleSigterm = () => { log.info("signal_received", { signal: "SIGTERM" }); void cleanShutdown(); };
|
|
3283
3320
|
pi.on("session_shutdown", async () => { await cleanShutdown(); });
|
|
3284
|
-
process.on("SIGINT",
|
|
3285
|
-
process.on("SIGTERM",
|
|
3321
|
+
process.on("SIGINT", handleSigint);
|
|
3322
|
+
process.on("SIGTERM", handleSigterm);
|
|
3286
3323
|
}
|