@aliou/pi-processes 0.10.9 → 0.11.1

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.
Files changed (40) hide show
  1. package/extensions/processes/client.ts +24 -2
  2. package/extensions/processes/commands/overview.ts +1 -1
  3. package/extensions/processes/components/overview-component.ts +121 -51
  4. package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +5 -8
  5. package/extensions/processes/config/types.ts +1 -1
  6. package/extensions/processes/handlers/commands.ts +21 -41
  7. package/extensions/processes/handlers/notifications.ts +3 -37
  8. package/extensions/processes/handlers/requests.ts +1 -54
  9. package/extensions/processes/handlers/subscriptions.ts +2 -35
  10. package/extensions/processes/hooks/event-bridge.ts +1 -1
  11. package/extensions/processes/index.ts +2 -2
  12. package/extensions/processes/notifications/service.ts +4 -1
  13. package/extensions/processes/notifications/types.ts +2 -2
  14. package/extensions/processes/tools/notify.ts +37 -130
  15. package/extensions/processes/tools/schema.ts +5 -2
  16. package/extensions/processes/tools/update/index.ts +15 -40
  17. package/extensions/processes-debug/index.ts +67 -0
  18. package/extensions/processes-dock/client.ts +2 -2
  19. package/extensions/processes-dock/widget/setup.ts +13 -38
  20. package/extensions/processes-logs/client.ts +2 -2
  21. package/extensions/processes-logs/commands/logs.ts +1 -1
  22. package/extensions/processes-logs/components/log-file-viewer.ts +135 -8
  23. package/extensions/processes-logs/components/log-overlay-component.ts +156 -82
  24. package/extensions/processes-logs/logs-client.ts +2 -23
  25. package/extensions/shared/log-line.ts +49 -2
  26. package/{src → extensions/shared}/protocol/broadcasts.ts +1 -1
  27. package/{src → extensions/shared}/protocol/channels.ts +1 -0
  28. package/{src → extensions/shared}/protocol/commands.ts +12 -1
  29. package/{src → extensions/shared}/protocol/index.ts +2 -0
  30. package/{src → extensions/shared}/protocol/notifications.ts +1 -1
  31. package/{src → extensions/shared}/protocol/requests.ts +1 -1
  32. package/extensions/shared/shortcut-hints.ts +150 -0
  33. package/extensions/shared/shortcuts-overlay.ts +229 -0
  34. package/extensions/shared/truncate.ts +189 -0
  35. package/package.json +3 -3
  36. package/src/utils/command-executor.ts +2 -1
  37. package/extensions/shared/output-payload.ts +0 -28
  38. package/src/get-manager.ts +0 -15
  39. package/src/utils/is-record.ts +0 -3
  40. /package/{src → extensions/shared}/protocol/logs.ts +0 -0
@@ -8,22 +8,44 @@
8
8
  */
9
9
 
10
10
  import type { EventBus } from "@earendil-works/pi-coding-agent";
11
+ import type { KillResult, ProcessInfo } from "../../src/types";
11
12
  import {
12
13
  CHANNELS,
13
14
  type CommandClearPayload,
14
15
  type CommandKillPayload,
15
16
  type CommandPinPayload,
16
17
  type CommandPinResult,
18
+ type CommandStartPayload,
19
+ type CommandStartResult,
17
20
  type ProcessProtocolConfig,
18
21
  type RequestCombinedOutputPayload,
19
22
  type RequestConfigPayload,
20
23
  type RequestGetPayload,
21
24
  type RequestListPayload,
22
- } from "../../src/protocol";
23
- import type { KillResult, ProcessInfo } from "../../src/types";
25
+ } from "../shared/protocol";
24
26
 
25
27
  export type ProcessLogLine = { type: "stdout" | "stderr"; text: string };
26
28
 
29
+ /**
30
+ * Start a managed process via the core extension's protocol channel. Resolves
31
+ * when the core handler replies. The started process is fully visible to the
32
+ * agent via the `process` tool (list, output, stop, etc.).
33
+ */
34
+ export function requestStart(
35
+ events: EventBus,
36
+ options: { name: string; command: string; cwd?: string },
37
+ ): Promise<CommandStartResult> {
38
+ return new Promise((resolve) => {
39
+ const payload: CommandStartPayload = {
40
+ name: options.name,
41
+ command: options.command,
42
+ cwd: options.cwd,
43
+ reply: (result) => resolve(result),
44
+ };
45
+ events.emit(CHANNELS.COMMAND_START, payload);
46
+ });
47
+ }
48
+
27
49
  export function requestProcessList(events: EventBus): ProcessInfo[] {
28
50
  let processes: ProcessInfo[] = [];
29
51
  const payload: RequestListPayload = {
@@ -4,9 +4,9 @@ import type {
4
4
  ExtensionCommandContext,
5
5
  Theme,
6
6
  } from "@earendil-works/pi-coding-agent";
7
- import type { ProcessProtocolConfig } from "../../../src/protocol";
8
7
  import type { ProcessInfo } from "../../../src/types";
9
8
  import { sanitizeForDisplay } from "../../shared/display-text";
9
+ import type { ProcessProtocolConfig } from "../../shared/protocol";
10
10
  import { requestConfig, requestProcess, requestProcessList } from "../client";
11
11
  import { OverviewComponent } from "../components/overview-component";
12
12
 
@@ -4,11 +4,10 @@ import {
4
4
  type Component,
5
5
  Input,
6
6
  Key,
7
- matchesKey,
7
+ parseKey,
8
8
  type TUI,
9
9
  visibleWidth,
10
10
  } from "@earendil-works/pi-tui";
11
- import { CHANNELS, type ProcessProtocolConfig } from "../../../src/protocol";
12
11
  import { LIVE_STATUSES, type ProcessInfo } from "../../../src/types";
13
12
  import { formatRuntime } from "../../../src/utils/format";
14
13
  import {
@@ -17,7 +16,20 @@ import {
17
16
  } from "../../shared/display-text";
18
17
  import { buildDroppedOutputLine, trimToBudget } from "../../shared/line-buffer";
19
18
  import { renderLogLine } from "../../shared/log-line";
20
- import { isOutputChangedPayload } from "../../shared/output-payload";
19
+ import {
20
+ CHANNELS,
21
+ type ProcessesOutputChangedPayload,
22
+ type ProcessProtocolConfig,
23
+ } from "../../shared/protocol";
24
+ import {
25
+ SHORTCUTS_KEY,
26
+ type ShortcutHint,
27
+ ShortcutHintsComponent,
28
+ } from "../../shared/shortcut-hints";
29
+ import {
30
+ type ShortcutGroup,
31
+ showShortcutsOverlay,
32
+ } from "../../shared/shortcuts-overlay";
21
33
  import { truncateToWidth } from "../../shared/truncate";
22
34
  import { LineComponent, LinesComponent, statusColor } from "../../shared/ui";
23
35
  import {
@@ -89,6 +101,8 @@ export class OverviewComponent implements Component {
89
101
  private runningProcessCount = 0;
90
102
  private readonly disposers: Array<() => void> = [];
91
103
  private disposed = false;
104
+ /** Disposer for the "?" shortcuts overlay, when open. */
105
+ private shortcutsHelp: (() => void) | null = null;
92
106
 
93
107
  constructor(private readonly opts: OverviewOptions) {
94
108
  this.configureFilterInput();
@@ -120,6 +134,34 @@ export class OverviewComponent implements Component {
120
134
  return panel.render(width);
121
135
  }
122
136
 
137
+ /**
138
+ * Key dispatch for normal mode, keyed by parsed key id. Close keys are
139
+ * handled in `handleInput` before the table is consulted.
140
+ */
141
+ private readonly keyActions: Record<string, () => void> = {
142
+ [Key.down]: () => this.moveSelection(1),
143
+ [Key.up]: () => this.moveSelection(-1),
144
+ [Key.enter]: () => void this.pinSelected(),
145
+ j: () => this.moveSelection(1),
146
+ k: () => this.moveSelection(-1),
147
+ J: () => this.scrollPreview(1),
148
+ K: () => this.scrollPreview(-1),
149
+ g: () => {
150
+ this.previewOffset = 0;
151
+ this.requestRender();
152
+ },
153
+ G: () => {
154
+ this.previewOffset = Math.max(0, this.previewLines.length - 1);
155
+ this.requestRender();
156
+ },
157
+ x: () => this.killSelected(),
158
+ c: () => this.clearFinished(),
159
+ s: () => this.cycleSort(),
160
+ f: () => this.cycleFilter(),
161
+ "/": () => this.startQuickFilter(),
162
+ [SHORTCUTS_KEY]: () => this.openShortcutsHelp(),
163
+ };
164
+
123
165
  handleInput(data: string): void {
124
166
  if (this.mode === "filter-typing") {
125
167
  this.filterInput.handleInput?.(data);
@@ -127,30 +169,13 @@ export class OverviewComponent implements Component {
127
169
  return;
128
170
  }
129
171
 
130
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
172
+ const key = parseKey(data);
173
+ if (key === "escape" || key === "ctrl+c" || key === "q" || key === "Q") {
131
174
  this.close();
132
175
  return;
133
176
  }
134
- if (data === "q" || data === "Q") {
135
- this.close();
136
- return;
137
- }
138
- if (matchesKey(data, Key.down) || data === "j") this.moveSelection(1);
139
- else if (matchesKey(data, Key.up) || data === "k") this.moveSelection(-1);
140
- else if (data === "J") this.scrollPreview(1);
141
- else if (data === "K") this.scrollPreview(-1);
142
- else if (data === "g") {
143
- this.previewOffset = 0;
144
- this.requestRender();
145
- } else if (data === "G") {
146
- this.previewOffset = Math.max(0, this.previewLines.length - 1);
147
- this.requestRender();
148
- } else if (data === "x") this.killSelected();
149
- else if (data === "c") this.clearFinished();
150
- else if (data === "s") this.cycleSort();
151
- else if (data === "f") this.cycleFilter();
152
- else if (data === "/") this.startQuickFilter();
153
- else if (matchesKey(data, Key.enter)) void this.pinSelected();
177
+
178
+ this.keyActions[key ?? ""]?.();
154
179
  }
155
180
 
156
181
  invalidate(): void {}
@@ -158,6 +183,8 @@ export class OverviewComponent implements Component {
158
183
  dispose(): void {
159
184
  if (this.disposed) return;
160
185
  this.disposed = true;
186
+ this.shortcutsHelp?.();
187
+ this.shortcutsHelp = null;
161
188
  for (const dispose of this.disposers.splice(0)) dispose();
162
189
  }
163
190
 
@@ -195,8 +222,8 @@ export class OverviewComponent implements Component {
195
222
  this.requestRender();
196
223
  }
197
224
 
198
- private handleOutputChanged(payload: unknown): void {
199
- if (!isOutputChangedPayload(payload)) return;
225
+ private handleOutputChanged(rawPayload: unknown): void {
226
+ const payload = rawPayload as ProcessesOutputChangedPayload;
200
227
  const selected = this.selectedProcess();
201
228
  if (!selected || selected.id !== payload.id) return;
202
229
  const appended = [
@@ -447,6 +474,19 @@ export class OverviewComponent implements Component {
447
474
  this.opts.onClose();
448
475
  }
449
476
 
477
+ /**
478
+ * Open the "?" shortcuts overlay on top of the overview. While open it
479
+ * captures input; closing it restores focus here. Disposed with the
480
+ * overview so a background close cannot leave it behind.
481
+ */
482
+ private openShortcutsHelp(): void {
483
+ if (this.shortcutsHelp) return;
484
+ this.shortcutsHelp = showShortcutsOverlay(this.opts.tui, {
485
+ theme: this.opts.theme,
486
+ groups: this.shortcutGroups(),
487
+ });
488
+ }
489
+
450
490
  private requestRender(): void {
451
491
  this.opts.tui.requestRender();
452
492
  }
@@ -658,35 +698,65 @@ export class OverviewComponent implements Component {
658
698
  }
659
699
 
660
700
  private buildFooter(): Component {
661
- return new LineComponent((width) => this.renderFooterLine(width));
662
- }
663
-
664
- private renderFooterLine(width: number): string {
665
- const t = this.opts.theme;
666
- const dim = (value: string) => t.fg("dim", value);
667
-
668
701
  if (this.mode === "filter-typing") {
669
- const rendered = this.filterInput.render(60)[0] ?? "";
670
- return truncateToWidth(
671
- `${dim("/")}${rendered} ${dim("enter")} apply ${dim("esc")} cancel`,
672
- width,
673
- "",
674
- true,
675
- );
702
+ return new LineComponent((width) => {
703
+ const dim = (value: string) => this.opts.theme.fg("dim", value);
704
+ const rendered = this.filterInput.render(60)[0] ?? "";
705
+ return truncateToWidth(
706
+ `${dim("/")}${rendered} ${dim("enter")} apply ${dim("esc")} cancel`,
707
+ width,
708
+ "",
709
+ true,
710
+ );
711
+ });
676
712
  }
713
+ return new ShortcutHintsComponent(
714
+ () => this.overviewHints(),
715
+ this.opts.theme,
716
+ );
717
+ }
718
+
719
+ /** Footer hint list for the overview. */
720
+ private overviewHints(): ShortcutHint[] {
721
+ return [
722
+ { key: "j/k", label: "move" },
723
+ { key: "J/K", label: "scroll" },
724
+ { key: "enter", label: this.renderPinHint() },
725
+ { key: "x", label: "kill" },
726
+ { key: "c", label: "clear" },
727
+ { key: "s", label: "sort" },
728
+ { key: "f", label: "filter" },
729
+ { key: "/", label: "find" },
730
+ { key: "g/G", label: "top/bot" },
731
+ { key: "q", label: "close" },
732
+ ];
733
+ }
677
734
 
678
- const keys = [
679
- `${dim("j/k")} move`,
680
- `${dim("J/K")} scroll`,
681
- `${dim("enter")} ${this.renderPinHint()}`,
682
- `${dim("x")} kill`,
683
- `${dim("c")} clear`,
684
- `${dim("s")} sort`,
685
- `${dim("f")} filter`,
686
- `${dim("/")} find`,
687
- `${dim("q")} close`,
735
+ /** Groups for the "?" shortcuts overlay. */
736
+ private shortcutGroups(): ShortcutGroup[] {
737
+ return [
738
+ {
739
+ title: "list",
740
+ rows: [
741
+ { keys: "j / k", description: "move selection" },
742
+ { keys: "g / G", description: "preview top / bottom" },
743
+ { keys: "enter", description: this.renderPinHint() },
744
+ { keys: "x", description: "kill" },
745
+ { keys: "c", description: "clear finished" },
746
+ { keys: "s", description: "sort" },
747
+ { keys: "f", description: "filter" },
748
+ { keys: "/", description: "find by name" },
749
+ ],
750
+ },
751
+ {
752
+ title: "preview",
753
+ rows: [{ keys: "J / K", description: "scroll output" }],
754
+ },
755
+ {
756
+ title: "general",
757
+ rows: [{ keys: "q", description: "close" }],
758
+ },
688
759
  ];
689
- return truncateToWidth(keys.join(" "), width, "", true);
690
760
  }
691
761
  }
692
762
 
@@ -73,10 +73,6 @@ export interface ConfigV0100 {
73
73
  * - widget.showStatusWidget is preserved (still controls the status widget).
74
74
  */
75
75
 
76
- function isRecord(value: unknown): value is Record<string, unknown> {
77
- return typeof value === "object" && value !== null && !Array.isArray(value);
78
- }
79
-
80
76
  function setIfDefined<T extends object, K extends keyof T>(
81
77
  target: T,
82
78
  key: K,
@@ -120,10 +116,11 @@ export function needsConfigV094ToV0100Migration(
120
116
  return false;
121
117
  }
122
118
 
123
- const root = config as Record<string, unknown>;
124
- const widget = isRecord(root.widget) ? root.widget : undefined;
125
-
126
- return widget?.dockDefaultState === "hidden";
119
+ // Widen to string | undefined: the incoming config predates the migration,
120
+ // so its dockDefaultState can be "hidden" even though the static type says
121
+ // it cannot.
122
+ const dockDefaultState: string | undefined = config.widget?.dockDefaultState;
123
+ return dockDefaultState === "hidden";
127
124
  }
128
125
 
129
126
  export function migrateConfigV094ToV0100(config: ConfigV094): ConfigV0100 {
@@ -5,7 +5,7 @@
5
5
  * Runtime config uses the shared protocol shape returned by REQUEST_CONFIG.
6
6
  */
7
7
 
8
- import type { ProcessProtocolConfig } from "../../../src/protocol";
8
+ import type { ProcessProtocolConfig } from "../../shared/protocol";
9
9
 
10
10
  export interface ExecutionConfig {
11
11
  /** Absolute shell executable used to run process commands. */
@@ -1,13 +1,13 @@
1
1
  import type { EventBus } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import type { ProcessManager } from "../../../src/manager";
4
+ import type { KillResult } from "../../../src/types";
4
5
  import {
5
6
  CHANNELS,
6
7
  type CommandClearPayload,
7
8
  type CommandKillPayload,
8
- } from "../../../src/protocol";
9
- import type { KillResult } from "../../../src/types";
10
- import { isRecord } from "../../../src/utils/is-record";
9
+ type CommandStartPayload,
10
+ } from "../../shared/protocol";
11
11
  import type { NotificationRegistry } from "../notifications/registry";
12
12
  import { killIntentionally } from "./kill-process";
13
13
 
@@ -17,9 +17,26 @@ export function registerCommandHandlers(
17
17
  notifications: NotificationRegistry,
18
18
  ): () => void {
19
19
  const disposers = [
20
+ events.on(CHANNELS.COMMAND_START, (payload) => {
21
+ const command = payload as CommandStartPayload;
22
+
23
+ try {
24
+ const info = manager.start(
25
+ command.name,
26
+ command.command,
27
+ command.cwd ?? process.cwd(),
28
+ );
29
+ notifications.register(info.id, {});
30
+ safeReply(command.reply, { ok: true, process: info });
31
+ } catch (err) {
32
+ safeReply(command.reply, {
33
+ ok: false,
34
+ error: err instanceof Error ? err.message : String(err),
35
+ });
36
+ }
37
+ }),
20
38
  events.on(CHANNELS.COMMAND_KILL, (payload) => {
21
39
  const command = payload as CommandKillPayload;
22
- if (!isCommandKillPayload(command)) return;
23
40
 
24
41
  void killIntentionally(manager, notifications, command.id, {
25
42
  signal: command.signal,
@@ -31,7 +48,6 @@ export function registerCommandHandlers(
31
48
  }),
32
49
  events.on(CHANNELS.COMMAND_CLEAR, (payload) => {
33
50
  const command = payload as CommandClearPayload;
34
- if (!isCommandClearPayload(command)) return;
35
51
 
36
52
  safeReply(command.reply, manager.clearFinished());
37
53
  }),
@@ -42,42 +58,6 @@ export function registerCommandHandlers(
42
58
  };
43
59
  }
44
60
 
45
- function isCommandKillPayload(
46
- payload: CommandKillPayload,
47
- ): payload is CommandKillPayload {
48
- return (
49
- isRecord(payload) &&
50
- typeof payload.id === "string" &&
51
- isOptionalSignal(payload.signal) &&
52
- isOptionalNumber(payload.timeoutMs) &&
53
- isReply(payload)
54
- );
55
- }
56
-
57
- function isCommandClearPayload(
58
- payload: CommandClearPayload,
59
- ): payload is CommandClearPayload {
60
- return isRecord(payload) && isReply(payload);
61
- }
62
-
63
- function isReply(
64
- payload: unknown,
65
- ): payload is { reply: (...args: never[]) => void } {
66
- return isRecord(payload) && typeof payload.reply === "function";
67
- }
68
-
69
- function isOptionalSignal(
70
- signal: unknown,
71
- ): signal is NodeJS.Signals | undefined {
72
- return signal === undefined || typeof signal === "string";
73
- }
74
-
75
- function isOptionalNumber(value: unknown): value is number | undefined {
76
- return (
77
- value === undefined || (typeof value === "number" && Number.isFinite(value))
78
- );
79
- }
80
-
81
61
  function safeReply<T>(reply: (result: T) => void, result: T): void {
82
62
  try {
83
63
  reply(result);
@@ -3,8 +3,7 @@ import type { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  CHANNELS,
5
5
  type ProcessProtocolNotificationPayload,
6
- } from "../../../src/protocol";
7
- import { isRecord } from "../../../src/utils/is-record";
6
+ } from "../../shared/protocol";
8
7
  import {
9
8
  attentionToSendOptions,
10
9
  sendProcessNotificationMessage,
@@ -83,8 +82,8 @@ export function registerNotificationDelivery(
83
82
 
84
83
  const disposeListener = events.on(
85
84
  CHANNELS.NOTIFICATION,
86
- (payload: unknown) => {
87
- if (!isNotificationPayload(payload)) return;
85
+ (rawPayload: unknown) => {
86
+ const payload = rawPayload as ProcessProtocolNotificationPayload;
88
87
 
89
88
  if (payload.kind === "log_match") {
90
89
  const now = performance.now();
@@ -113,36 +112,3 @@ export function registerNotificationDelivery(
113
112
  resetWindow();
114
113
  };
115
114
  }
116
-
117
- function isNotificationPayload(
118
- payload: unknown,
119
- ): payload is ProcessProtocolNotificationPayload {
120
- if (!isRecord(payload)) return false;
121
- if (!isNotificationKind(payload.kind)) return false;
122
- if (typeof payload.processId !== "string") return false;
123
- if (typeof payload.processName !== "string") return false;
124
- if (typeof payload.command !== "string") return false;
125
- if (typeof payload.timestamp !== "number") return false;
126
- if (typeof payload.summary !== "string") return false;
127
- if (
128
- payload.attention !== "turn" &&
129
- payload.attention !== "context" &&
130
- payload.attention !== "ignore"
131
- ) {
132
- return false;
133
- }
134
- return true;
135
- }
136
-
137
- function isNotificationKind(
138
- value: unknown,
139
- ): value is ProcessProtocolNotificationPayload["kind"] {
140
- return (
141
- value === "success" ||
142
- value === "failure" ||
143
- value === "crash" ||
144
- value === "killed" ||
145
- value === "log_match" ||
146
- value === "log_match_suppressed"
147
- );
148
- }
@@ -9,8 +9,7 @@ import {
9
9
  type RequestListPayload,
10
10
  type RequestLogFilesPayload,
11
11
  type RequestOutputPayload,
12
- } from "../../../src/protocol";
13
- import { isRecord } from "../../../src/utils/is-record";
12
+ } from "../../shared/protocol";
14
13
  import type { ProcessProtocolConfig } from "../config";
15
14
 
16
15
  export function registerRequestHandlers(
@@ -21,43 +20,36 @@ export function registerRequestHandlers(
21
20
  const disposers = [
22
21
  events.on(CHANNELS.REQUEST_LIST, (payload) => {
23
22
  const request = payload as RequestListPayload;
24
- if (!isRequestListPayload(request)) return;
25
23
 
26
24
  request.reply(manager.list());
27
25
  }),
28
26
  events.on(CHANNELS.REQUEST_GET, (payload) => {
29
27
  const request = payload as RequestGetPayload;
30
- if (!isIdRequest(request)) return;
31
28
 
32
29
  request.reply(manager.get(request.id));
33
30
  }),
34
31
  events.on(CHANNELS.REQUEST_OUTPUT, (payload) => {
35
32
  const request = payload as RequestOutputPayload;
36
- if (!isIdRequest(request)) return;
37
33
 
38
34
  request.reply(manager.getOutput(request.id, request.tailLines));
39
35
  }),
40
36
  events.on(CHANNELS.REQUEST_COMBINED_OUTPUT, (payload) => {
41
37
  const request = payload as RequestCombinedOutputPayload;
42
- if (!isIdRequest(request)) return;
43
38
 
44
39
  request.reply(manager.getCombinedOutput(request.id, request.tailLines));
45
40
  }),
46
41
  events.on(CHANNELS.REQUEST_LOG_FILES, (payload) => {
47
42
  const request = payload as RequestLogFilesPayload;
48
- if (!isIdRequest(request)) return;
49
43
 
50
44
  request.reply(manager.getLogFiles(request.id));
51
45
  }),
52
46
  events.on(CHANNELS.REQUEST_FILE_SIZE, (payload) => {
53
47
  const request = payload as RequestFileSizePayload;
54
- if (!isIdRequest(request)) return;
55
48
 
56
49
  request.reply(manager.getFileSize(request.id));
57
50
  }),
58
51
  events.on(CHANNELS.REQUEST_CONFIG, (payload) => {
59
52
  const request = payload as RequestConfigPayload;
60
- if (!isRequestConfigPayload(request)) return;
61
53
 
62
54
  request.reply(getConfig());
63
55
  }),
@@ -67,48 +59,3 @@ export function registerRequestHandlers(
67
59
  for (const dispose of disposers) dispose();
68
60
  };
69
61
  }
70
-
71
- function isRequestListPayload(
72
- payload: RequestListPayload,
73
- ): payload is RequestListPayload {
74
- return isRecord(payload) && isReply(payload);
75
- }
76
-
77
- function isRequestConfigPayload(
78
- payload: RequestConfigPayload,
79
- ): payload is RequestConfigPayload {
80
- return isRecord(payload) && isReply(payload);
81
- }
82
-
83
- function isIdRequest(
84
- payload:
85
- | RequestGetPayload
86
- | RequestOutputPayload
87
- | RequestCombinedOutputPayload
88
- | RequestLogFilesPayload
89
- | RequestFileSizePayload,
90
- ): payload is
91
- | RequestGetPayload
92
- | RequestOutputPayload
93
- | RequestCombinedOutputPayload
94
- | RequestLogFilesPayload
95
- | RequestFileSizePayload {
96
- return (
97
- isRecord(payload) &&
98
- typeof payload.id === "string" &&
99
- isOptionalNumber(payload.tailLines) &&
100
- isReply(payload)
101
- );
102
- }
103
-
104
- function isReply(
105
- payload: unknown,
106
- ): payload is { reply: (...args: never[]) => void } {
107
- return isRecord(payload) && typeof payload.reply === "function";
108
- }
109
-
110
- function isOptionalNumber(value: unknown): value is number | undefined {
111
- return (
112
- value === undefined || (typeof value === "number" && Number.isFinite(value))
113
- );
114
- }
@@ -1,13 +1,12 @@
1
1
  import type { EventBus } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import type { ProcessManager } from "../../../src/manager";
4
+ import { buildDroppedOutputLine } from "../../shared/line-buffer";
4
5
  import {
5
6
  CHANNELS,
6
7
  type LogsSubscribePayload,
7
8
  type LogsUnsubscribePayload,
8
- } from "../../../src/protocol";
9
- import { isRecord } from "../../../src/utils/is-record";
10
- import { buildDroppedOutputLine } from "../../shared/line-buffer";
9
+ } from "../../shared/protocol";
11
10
 
12
11
  interface LogSubscriber {
13
12
  subscriberId: string;
@@ -22,7 +21,6 @@ export function registerLogSubscriptions(
22
21
 
23
22
  const disposeSubscribe = events.on(CHANNELS.LOGS_SUBSCRIBE, (payload) => {
24
23
  const request = payload as LogsSubscribePayload;
25
- if (!isLogsSubscribePayload(request)) return;
26
24
 
27
25
  const processInfo = manager.get(request.processId);
28
26
 
@@ -51,7 +49,6 @@ export function registerLogSubscriptions(
51
49
 
52
50
  const disposeUnsubscribe = events.on(CHANNELS.LOGS_UNSUBSCRIBE, (payload) => {
53
51
  const request = payload as LogsUnsubscribePayload;
54
- if (!isLogsUnsubscribePayload(request)) return;
55
52
 
56
53
  subscribers.delete(request.subscriberId);
57
54
  });
@@ -112,33 +109,3 @@ function removeStaleSubscribers(
112
109
  if (!manager.get(subscriber.processId)) subscribers.delete(subscriberId);
113
110
  }
114
111
  }
115
-
116
- function isLogsSubscribePayload(
117
- payload: LogsSubscribePayload,
118
- ): payload is LogsSubscribePayload {
119
- return (
120
- isRecord(payload) &&
121
- typeof payload.subscriberId === "string" &&
122
- typeof payload.processId === "string" &&
123
- isOptionalNumber(payload.tailLines) &&
124
- isReply(payload)
125
- );
126
- }
127
-
128
- function isLogsUnsubscribePayload(
129
- payload: LogsUnsubscribePayload,
130
- ): payload is LogsUnsubscribePayload {
131
- return isRecord(payload) && typeof payload.subscriberId === "string";
132
- }
133
-
134
- function isReply(
135
- payload: unknown,
136
- ): payload is { reply: (...args: never[]) => void } {
137
- return isRecord(payload) && typeof payload.reply === "function";
138
- }
139
-
140
- function isOptionalNumber(value: unknown): value is number | undefined {
141
- return (
142
- value === undefined || (typeof value === "number" && Number.isFinite(value))
143
- );
144
- }
@@ -1,7 +1,7 @@
1
1
  import type { EventBus } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import type { ProcessManager } from "../../../src/manager";
4
- import { CHANNELS } from "../../../src/protocol";
4
+ import { CHANNELS } from "../../shared/protocol";
5
5
 
6
6
  export function registerEventBridge(
7
7
  events: EventBus,
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { getManager } from "../../src/get-manager";
2
+ import { ProcessManager } from "../../src/manager";
3
3
  import { isWindowsPlatform } from "../../src/utils/platform";
4
4
  import { registerClearCommand } from "./commands/clear";
5
5
  import { registerKillCommand } from "./commands/kill";
@@ -40,7 +40,7 @@ export default async function processesExtension(
40
40
  await loadProcessConfig();
41
41
  registerMigrationMessageNotifications(pi);
42
42
 
43
- const manager = getManager({
43
+ const manager = new ProcessManager({
44
44
  getConfiguredShellPath: () => configLoader.getConfig().execution.shellPath,
45
45
  });
46
46
  const notifications = createNotificationRegistry();