@comput/pi-intercom 0.3.0 → 0.4.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.
Files changed (58) hide show
  1. package/README.md +137 -25
  2. package/dist/activity.d.ts +12 -0
  3. package/dist/activity.js +73 -0
  4. package/dist/activity.js.map +1 -0
  5. package/dist/config.d.ts +39 -0
  6. package/dist/config.js +182 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +186 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/launcher.d.ts +17 -0
  12. package/dist/launcher.js +77 -0
  13. package/dist/launcher.js.map +1 -0
  14. package/dist/monitor-launcher.d.ts +17 -0
  15. package/dist/monitor-launcher.js +136 -0
  16. package/dist/monitor-launcher.js.map +1 -0
  17. package/dist/monitor-view.d.ts +6 -0
  18. package/dist/monitor-view.js +134 -0
  19. package/dist/monitor-view.js.map +1 -0
  20. package/dist/monitor.d.ts +22 -0
  21. package/dist/monitor.js +171 -0
  22. package/dist/monitor.js.map +1 -0
  23. package/dist/observability.d.ts +79 -0
  24. package/dist/observability.js +208 -0
  25. package/dist/observability.js.map +1 -0
  26. package/dist/reports.d.ts +12 -0
  27. package/dist/reports.js +160 -0
  28. package/dist/reports.js.map +1 -0
  29. package/dist/runtime.d.ts +59 -0
  30. package/dist/runtime.js +458 -0
  31. package/dist/runtime.js.map +1 -0
  32. package/dist/snapshot.d.ts +15 -0
  33. package/dist/snapshot.js +128 -0
  34. package/dist/snapshot.js.map +1 -0
  35. package/dist/transport.d.ts +24 -0
  36. package/dist/transport.js +122 -0
  37. package/dist/transport.js.map +1 -0
  38. package/dist/worker-status.d.ts +26 -0
  39. package/dist/worker-status.js +76 -0
  40. package/dist/worker-status.js.map +1 -0
  41. package/package.json +13 -7
  42. package/references/observability.md +66 -60
  43. package/references/roadmap.md +68 -0
  44. package/src/activity.ts +66 -0
  45. package/src/config.ts +1 -1
  46. package/src/index.ts +51 -23
  47. package/src/monitor-launcher.ts +110 -0
  48. package/src/monitor-view.ts +123 -0
  49. package/src/monitor.ts +143 -0
  50. package/src/observability.ts +6 -1
  51. package/src/reports.ts +122 -0
  52. package/src/runtime.ts +45 -7
  53. package/src/{dashboard.ts → snapshot.ts} +9 -60
  54. package/src/transport.ts +11 -2
  55. package/src/worker-status.ts +66 -0
  56. package/dashboard/app.js +0 -182
  57. package/dashboard/index.html +0 -41
  58. package/dashboard/style.css +0 -82
package/dist/index.js ADDED
@@ -0,0 +1,186 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import { Type } from 'typebox';
4
+ import { getAgentDir, SessionManager, SettingsManager, truncateHead, withFileMutationQueue } from '@earendil-works/pi-coding-agent';
5
+ import { ConfigStore } from './config.js';
6
+ import { Intercom, UNSUPPORTED_CANCELLATION } from './runtime.js';
7
+ import { launchers } from './launcher.js';
8
+ import { ensureMonitorPane } from './monitor-launcher.js';
9
+ import { createActivityTracker } from './activity.js';
10
+ const to = Type.Object({ to: Type.String({ minLength: 1, maxLength: 128 }) });
11
+ const tools = [
12
+ ['create_worker', 'Coordinator only. Launch one anonymous worker in projectDirectory (default root). No assignment, configuration, or registration wait.', Type.Object({ projectDirectory: Type.Optional(Type.String()) })],
13
+ ['configure_worker', 'Coordinator only. Write explicit reported connection details and responsibility to config. Does not notify, reload, or start work. Use reload_worker separately.', Type.Object({ sessionId: Type.String(), port: Type.Integer({ minimum: 1, maximum: 65535 }), projectDirectory: Type.String(), name: Type.String({ minLength: 1, maxLength: 128 }), description: Type.String({ minLength: 1, maxLength: 4096 }) })],
14
+ ['reload_worker', 'Coordinator only. Ask worker to reread responsibility and synchronize names. NOT Pi extension reload, cancellation, restart, or work assignment.', to],
15
+ ['send', 'Configured sessions only. Send an explicit work/progress/findings message by recipient name. Idle delivery starts a turn; busy delivery steers at supported boundaries. Receipt is not an agent reply. Progress questions do not cancel assignments.', Type.Object({ to: Type.String(), message: Type.String({ minLength: 1, maxLength: 48000 }) })],
16
+ ['worker_status', 'Configured sessions only. Read paginated JSON worker observations and public reports from local files, optionally by worker name. Last-observed status is not live liveness or task completion; reports are self-reported, not approval. No worker prompts, live probing or Telegram dependency. Follow nextOffset for further pages; null report means no readable active report.', Type.Object({ name: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })), offset: Type.Optional(Type.Integer({ minimum: 0 })), limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 20 })) })],
17
+ ['list', 'Configured sessions only. Read current saved names, responsibilities, session IDs, roles and ports. No live probing. Output limited to 50 KiB/2000 lines; full data remains in config.json.', Type.Object({})],
18
+ ['request_status', 'Coordinator only. Ask worker extension to send an independent sessionId/port/busy report without a model turn on worker. No synchronous status reply.', to],
19
+ ['report_work', 'Configured, responsibility-loaded workers only. Store one public report (blocked, needs_decision, ready_for_review) and notify the coordinator; clear retires it. Summary required except clear (omit or empty). Never include private reasoning, credentials, raw tool payloads, or secrets. Receipt is not approval, verified completion, or permission for follow-up work.', Type.Object({ status: Type.String({ enum: ['blocked', 'needs_decision', 'ready_for_review', 'clear'] }), summary: Type.Optional(Type.String({ maxLength: 2000 })) })],
20
+ ['report_status', 'Worker only (including anonymous). Send runtime sessionId/port/busy report to coordinator. This is NOT registration and creates no config entry.', Type.Object({})],
21
+ ['stop_worker', UNSUPPORTED_CANCELLATION, to],
22
+ ['close_worker', UNSUPPORTED_CANCELLATION, to],
23
+ ['resume_worker', 'Coordinator only. Launch saved session by ID in configured directory. No replacement/removal on failure; no old assignment is automatically started.', to],
24
+ ['remove_worker', 'Coordinator only. Remove config entry only; never stop process or delete session files. A running worker must be explicitly closed before removal. Stop/close are disabled on current host: arrange explicit user closure first.', to],
25
+ ['set_multiplexer', 'Coordinator only. Set herdr (default, Windows/Linux) or none (separate visible Windows terminals; unsupported on Linux) for future launches. Never move/restart existing workers or fall back.', Type.Object({ multiplexer: Type.String({ enum: ['herdr', 'none'] }) })],
26
+ ];
27
+ // Launchers pass no --session-dir. Match child Pi's env > per-cwd settings >
28
+ // default lookup, resolving relative paths against the child's cwd, not ours.
29
+ // SettingsManager's public getter applies Pi's own path/tilde normalization.
30
+ export function resumeSessionDirectory(cwd, envSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR, agentDir = getAgentDir()) {
31
+ const settings = envSessionDir
32
+ ? SettingsManager.inMemory({ sessionDir: envSessionDir })
33
+ : SettingsManager.create(cwd, path.resolve(cwd, agentDir));
34
+ const sessionDir = settings.getSessionDir();
35
+ return sessionDir ? path.resolve(cwd, sessionDir) : undefined;
36
+ }
37
+ // Participate in Pi's own per-file mutation queue as well as Intercom serialization.
38
+ class PiConfigStore extends ConfigStore {
39
+ update(id, mutate, assertValid) {
40
+ return withFileMutationQueue(this.file, () => super.update(id, mutate, assertValid));
41
+ }
42
+ }
43
+ export default function intercomExtension(pi) {
44
+ let runtime;
45
+ let context;
46
+ let generation = 0;
47
+ let activityOutcome = 'snapshot';
48
+ const activity = createActivityTracker((phase, detail) => {
49
+ runtime?.recordObservation('host.activity', { busy: phase !== 'idle', phase, detail, outcome: activityOutcome });
50
+ });
51
+ const launcher = launchers({
52
+ extension: fileURLToPath(import.meta.url),
53
+ sessionExists: async (cwd, id) => (await SessionManager.list(cwd, resumeSessionDirectory(cwd))).some(s => s.id === id),
54
+ });
55
+ pi.on('session_start', async (_event, ctx) => {
56
+ const started = ++generation;
57
+ activity.reset();
58
+ const oldRuntime = runtime;
59
+ runtime = undefined;
60
+ await oldRuntime?.close();
61
+ if (started !== generation)
62
+ return;
63
+ context = ctx;
64
+ if (!['win32', 'linux'].includes(process.platform) || ctx.mode !== 'tui')
65
+ throw new Error('PiIntercom requires Windows or Linux interactive Pi. No resources started.');
66
+ if (!ctx.isProjectTrusted())
67
+ throw new Error('PiIntercom requires project trust before honoring shared project configuration.');
68
+ runtime = new Intercom({
69
+ cwd: ctx.cwd,
70
+ sessionId: () => context.sessionManager.getSessionId(),
71
+ busy: () => !context.isIdle(),
72
+ // Always supply the supported busy mode: the arrival snapshot can become
73
+ // stale before Pi checks it. Pi still starts a normal turn when idle.
74
+ deliver: content => pi.sendUserMessage(content, { deliverAs: 'steer' }),
75
+ setName: async (name) => { if (pi.getSessionName() !== name)
76
+ pi.setSessionName(name); await launcher.syncName(name); },
77
+ notify: text => ctx.ui.notify(text, 'info'),
78
+ }, { launch: launcher.launch, store: root => new PiConfigStore(root) });
79
+ const current = runtime;
80
+ try {
81
+ await current.start();
82
+ }
83
+ catch (e) {
84
+ if (runtime === current)
85
+ runtime = undefined;
86
+ ctx.ui.notify(String(e), 'error');
87
+ return;
88
+ }
89
+ if (started !== generation) {
90
+ await current.close();
91
+ return;
92
+ }
93
+ try {
94
+ if ((await current.state()).me?.coordinator) {
95
+ try {
96
+ await current.retireDashboardMetadata();
97
+ }
98
+ catch (error) {
99
+ if (started !== generation)
100
+ return;
101
+ ctx.ui.notify(`Legacy dashboard metadata cleanup failed; messaging remains available: ${String(error)}`, 'warning');
102
+ }
103
+ if (started !== generation)
104
+ return;
105
+ const monitor = await ensureMonitorPane({
106
+ root: current.store.root, sessionId: ctx.sessionManager.getSessionId(),
107
+ script: fileURLToPath(new URL('../dist/monitor.js', import.meta.url)),
108
+ assertCurrent: () => { if (started !== generation || runtime !== current)
109
+ throw new Error('Coordinator session changed'); },
110
+ });
111
+ if (started !== generation)
112
+ return;
113
+ ctx.ui.notify(`Intercom status monitor: ${monitor.outcome}${monitor.pane ? ` (${monitor.pane})` : ''}`, 'info');
114
+ }
115
+ }
116
+ catch (error) {
117
+ if (started !== generation)
118
+ return;
119
+ ctx.ui.notify(String(error), 'warning');
120
+ }
121
+ });
122
+ pi.on('session_shutdown', async () => {
123
+ generation++;
124
+ activity.reset();
125
+ const oldRuntime = runtime;
126
+ runtime = undefined;
127
+ context = undefined;
128
+ await oldRuntime?.close();
129
+ });
130
+ pi.on('agent_start', async (_event, ctx) => {
131
+ context = ctx;
132
+ activityOutcome = 'started';
133
+ try {
134
+ activity.start();
135
+ }
136
+ finally {
137
+ activityOutcome = 'snapshot';
138
+ }
139
+ });
140
+ pi.on('agent_settled', async (_event, ctx) => {
141
+ context = ctx;
142
+ activityOutcome = 'settled';
143
+ try {
144
+ activity.settled();
145
+ }
146
+ finally {
147
+ activityOutcome = 'snapshot';
148
+ }
149
+ });
150
+ pi.on('message_update', (event, ctx) => {
151
+ context = ctx;
152
+ // Inspect the event discriminator only. Never access reasoning/text payloads.
153
+ activity.message(event.assistantMessageEvent.type);
154
+ });
155
+ pi.on('tool_execution_start', (event, ctx) => {
156
+ context = ctx;
157
+ activity.toolStart(event.toolCallId, event.toolName);
158
+ });
159
+ pi.on('tool_execution_end', (event, ctx) => {
160
+ context = ctx;
161
+ activity.toolEnd(event.toolCallId);
162
+ });
163
+ pi.on('before_agent_start', async (event, ctx) => {
164
+ context = ctx;
165
+ if (!runtime)
166
+ return;
167
+ const { me } = await runtime.state();
168
+ const loaded = runtime.responsibility;
169
+ return { systemPrompt: event.systemPrompt + '\n\nPiIntercom: communicates; Pi decides orchestration. Responsibility is not a work assignment. Do not implement unrelated work from findings. After assigned work, report as instructed and wait; do not autonomously exit. Progress questions require reporting and continuing unless explicitly redirected. stop_worker/close_worker are disabled due to host cancellation blocker.\n' + (me && loaded ? `Identity: ${loaded.name} (${me.sessionId}). Responsibility: ${loaded.description}.` : 'Anonymous/unloaded worker: wait for coordinator configuration and separate reload; do not treat registration as authorization to work.') };
170
+ });
171
+ for (const [operation, description, parameters] of tools) {
172
+ pi.registerTool({
173
+ name: `intercom_${operation}`, label: `Intercom ${operation}`, description, parameters,
174
+ async execute(_id, args, signal, _update, ctx) {
175
+ signal?.throwIfAborted();
176
+ context = ctx;
177
+ if (!runtime)
178
+ throw new Error('PiIntercom not initialized; inspect startup error.');
179
+ const result = await runtime.tool(operation, args);
180
+ const output = truncateHead(JSON.stringify(result, null, 2));
181
+ return { content: [{ type: 'text', text: output.content + (output.truncated ? `\n[Truncated; full shared configuration: ${runtime.store.file}]` : '') }], details: {} };
182
+ },
183
+ });
184
+ }
185
+ }
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { LaunchRequest } from './runtime.js';
2
+ export type Run = (file: string, args: string[]) => Promise<string>;
3
+ export declare const run: Run;
4
+ export declare const psQuote: (value: string) => string;
5
+ export declare const shQuote: (value: string) => string;
6
+ export declare const encoded: (script: string) => string;
7
+ export interface LauncherOptions {
8
+ extension: string;
9
+ platform?: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ run?: Run;
12
+ sessionExists(cwd: string, id: string): Promise<boolean>;
13
+ }
14
+ export declare function launchers(options: LauncherOptions): {
15
+ launch: (request: LaunchRequest) => Promise<unknown>;
16
+ syncName: (name: string) => Promise<void>;
17
+ };
@@ -0,0 +1,77 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { fail } from './config.js';
4
+ export const run = async (file, args) => {
5
+ const { stdout } = await promisify(execFile)(file, args, { windowsHide: true, timeout: 40000, maxBuffer: 1024 * 1024 });
6
+ return stdout;
7
+ };
8
+ export const psQuote = (value) => `'${value.replaceAll("'", "''")}'`;
9
+ export const shQuote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
10
+ export const encoded = (script) => Buffer.from(script, 'utf16le').toString('base64');
11
+ export function launchers(options) {
12
+ const exec = options.run ?? run, env = options.env ?? process.env;
13
+ async function launch(request) {
14
+ const platform = options.platform ?? process.platform;
15
+ if (platform !== 'win32' && platform !== 'linux')
16
+ fail('launchers support Windows and Linux only');
17
+ if (platform === 'linux' && request.multiplexer === 'none')
18
+ fail('Linux worker launching requires Herdr; none is Windows-only. No terminal fallback');
19
+ if (request.sessionId && !await options.sessionExists(request.cwd, request.sessionId))
20
+ fail('saved Pi session not found in project directory; never-used sessions may not be persisted. Config unchanged; no replacement launched');
21
+ const args = ['-e', options.extension, ...(request.sessionId ? ['--session', request.sessionId] : [])];
22
+ if (request.multiplexer === 'herdr') {
23
+ if (env.HERDR_ENV !== '1' || !env.HERDR_WORKSPACE_ID)
24
+ fail('Herdr launcher requires this Pi to run inside a Herdr-managed workspace; no fallback');
25
+ let created;
26
+ try {
27
+ created = JSON.parse(await exec('herdr', ['tab', 'create', '--workspace', env.HERDR_WORKSPACE_ID, '--cwd', request.cwd, '--label', 'Intercom (anonymous)', '--no-focus']));
28
+ }
29
+ catch (e) {
30
+ fail(`Herdr tab launch failed: ${String(e)}; no fallback or cleanup`);
31
+ }
32
+ const pane = created.result?.root_pane?.pane_id, tab = created.result?.tab?.tab_id;
33
+ if (!pane || !tab)
34
+ fail('Herdr creation response missing returned pane/tab IDs; no guessed targeting or cleanup');
35
+ // Herdr's Windows agent-start wrapper uses Start-Process on `pi`, which
36
+ // can resolve to a PowerShell shim and fail with invalid Win32 application.
37
+ // Run an explicit PowerShell command in the returned shell pane instead.
38
+ // This acknowledges command submission only, not Pi readiness/registration.
39
+ const script = `$ErrorActionPreference='Stop'; Set-Location -LiteralPath ${psQuote(request.cwd)}; & (Get-Command pi.ps1 -ErrorAction Stop).Source ${args.map(psQuote).join(' ')}; if ($LASTEXITCODE -ne 0) { Write-Error ('Pi exited with code ' + $LASTEXITCODE) }`;
40
+ const command = platform === 'win32'
41
+ ? `powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded(script)}`
42
+ : `sh -c ${shQuote(`cd ${shQuote(request.cwd)} && exec pi ${args.map(shQuote).join(' ')}`)}`;
43
+ try {
44
+ await exec('herdr', ['pane', 'run', pane, command]);
45
+ }
46
+ catch (e) {
47
+ fail(`Herdr Pi command submission failed in tab ${tab}, pane ${pane}: ${String(e)}. Tab/process may remain; no automatic cleanup/retry/replacement`);
48
+ }
49
+ return { commandSubmitted: true, tab, pane, piReadiness: 'not observed; inspect pane for startup failures', registrationAwaited: false };
50
+ }
51
+ if (request.multiplexer !== 'none')
52
+ fail('unsupported multiplexer');
53
+ // Use encoded PowerShell rather than constructing cmd.exe command strings.
54
+ // The outer process reports only successful visible terminal creation, not Pi readiness.
55
+ const child = `$ErrorActionPreference='Stop'; Get-ChildItem Env: | Where-Object { $_.Name -like 'HERDR_*' -or $_.Name -like 'PI_SESSION_*' } | ForEach-Object { Remove-Item ('Env:' + $_.Name) }; Set-Location -LiteralPath ${psQuote(request.cwd)}; & (Get-Command pi.cmd -ErrorAction Stop).Source ${args.map(psQuote).join(' ')}; if ($LASTEXITCODE -ne 0) { Write-Error ('Pi exited with code ' + $LASTEXITCODE) }`;
56
+ const outer = `$ErrorActionPreference='Stop'; $null=Get-Command pi.cmd -ErrorAction Stop; $p=Start-Process -FilePath (Join-Path $PSHOME 'powershell.exe') -WorkingDirectory ${psQuote(request.cwd)} -ArgumentList @('-NoProfile','-NoExit','-EncodedCommand',${psQuote(encoded(child))}) -WindowStyle Normal -PassThru; $p.Id`;
57
+ const result = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded(outer)]);
58
+ const pid = Number(result.trim());
59
+ if (!Number.isInteger(pid) || pid <= 0)
60
+ fail('visible terminal launch returned no process ID; outcome unknown');
61
+ return { terminalLaunched: true, pid, piReadiness: 'not observed; inspect visible terminal for startup failures', registrationAwaited: false };
62
+ }
63
+ async function syncName(name) {
64
+ if (env.HERDR_ENV !== '1')
65
+ return;
66
+ // Resolve inherited pane context, including panes moved since launch.
67
+ if (!env.HERDR_PANE_ID)
68
+ fail('Herdr tab name sync requires caller pane identity');
69
+ const data = JSON.parse(await exec('herdr', ['pane', 'current', '--current']));
70
+ const tab = data.result?.pane?.tab_id;
71
+ if (typeof tab !== 'string')
72
+ fail('Herdr current-pane response missing tab_id; tab name not synchronized');
73
+ await exec('herdr', ['tab', 'rename', tab, name]);
74
+ }
75
+ return { launch, syncName };
76
+ }
77
+ //# sourceMappingURL=launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAInC,MAAM,CAAC,MAAM,GAAG,GAAQ,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IAC3C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;IACxH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AACF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;AAC7E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC;AAClF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAQ7F,MAAM,UAAU,SAAS,CAAC,OAAwB;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAClE,KAAK,UAAU,MAAM,CAAC,OAAsB;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;QACtD,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,OAAO;YAAE,IAAI,CAAC,0CAA0C,CAAC,CAAC;QACnG,IAAI,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM;YAAE,IAAI,CAAC,mFAAmF,CAAC,CAAC;QACtJ,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,CAAC;YAAE,IAAI,CAAC,sIAAsI,CAAC,CAAC;QACpO,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACvG,IAAI,OAAO,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC;YACpC,IAAI,GAAG,CAAC,SAAS,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB;gBAAE,IAAI,CAAC,sFAAsF,CAAC,CAAC;YACnJ,IAAI,OAAqF,CAAC;YAC1F,IAAI,CAAC;gBAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,kBAAkB,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,sBAAsB,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;YAAC,CAAC;YACnL,OAAO,CAAC,EAAE,CAAC;gBAAC,IAAI,CAAC,4BAA4B,MAAM,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC;YAAC,CAAC;YACpF,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;YACnF,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,wFAAwF,CAAC,CAAC;YAClH,wEAAwE;YACxE,4EAA4E;YAC5E,yEAAyE;YACzE,4EAA4E;YAC5E,MAAM,MAAM,GAAG,4DAA4D,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qDAAqD,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,qFAAqF,CAAC;YACrQ,MAAM,OAAO,GAAG,QAAQ,KAAK,OAAO;gBAClC,CAAC,CAAC,qEAAqE,OAAO,CAAC,MAAM,CAAC,EAAE;gBACxF,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/F,IAAI,CAAC;gBAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,CAAC;gBAAC,IAAI,CAAC,6CAA6C,GAAG,UAAU,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,kEAAkE,CAAC,CAAC;YAAC,CAAC;YACnK,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,iDAAiD,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC;QAC3I,CAAC;QACD,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM;YAAE,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACpE,2EAA2E;QAC3E,yFAAyF;QACzF,MAAM,KAAK,GAAG,gNAAgN,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qDAAqD,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,qFAAqF,CAAC;QACxZ,MAAM,KAAK,GAAG,gKAAgK,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,6DAA6D,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,wCAAwC,CAAC;QAC/T,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClH,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,IAAI,CAAC,iEAAiE,CAAC,CAAC;QAChH,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,6DAA6D,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC;IACjJ,CAAC;IACD,KAAK,UAAU,QAAQ,CAAC,IAAY;QAClC,IAAI,GAAG,CAAC,SAAS,KAAK,GAAG;YAAE,OAAO;QAClC,sEAAsE;QACtE,IAAI,CAAC,GAAG,CAAC,aAAa;YAAE,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAClF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;QAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;QACtC,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,IAAI,CAAC,uEAAuE,CAAC,CAAC;QAC3G,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type Run } from './launcher.js';
2
+ export interface MonitorLaunchOptions {
3
+ root: string;
4
+ sessionId: string;
5
+ script: string;
6
+ node?: string;
7
+ platform?: string;
8
+ env?: NodeJS.ProcessEnv;
9
+ run?: Run;
10
+ assertCurrent?(): void;
11
+ }
12
+ /** Pane ownership is separate from agent config. Pending records fence uncertain
13
+ * mutations across reloads; neither errors nor absence of readiness cause retries. */
14
+ export declare function ensureMonitorPane(options: MonitorLaunchOptions): Promise<{
15
+ outcome: string;
16
+ pane?: string;
17
+ }>;
@@ -0,0 +1,136 @@
1
+ import path from 'node:path';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { access, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
4
+ import { run, shQuote, psQuote, encoded } from './launcher.js';
5
+ /** Pane ownership is separate from agent config. Pending records fence uncertain
6
+ * mutations across reloads; neither errors nor absence of readiness cause retries. */
7
+ export async function ensureMonitorPane(options) {
8
+ const env = options.env ?? process.env;
9
+ if (env.HERDR_ENV !== '1')
10
+ return { outcome: 'unavailable: coordinator is not inside Herdr' };
11
+ if (!env.HERDR_PANE_ID)
12
+ throw new Error('Monitor requires caller pane identity; no focused-pane fallback');
13
+ const platform = options.platform ?? process.platform;
14
+ if (!['win32', 'linux'].includes(platform))
15
+ throw new Error('Monitor launcher supports Windows/Linux only');
16
+ const exec = options.run ?? run;
17
+ const check = () => options.assertCurrent?.();
18
+ const call = async (args) => {
19
+ check();
20
+ const output = await exec('herdr', args);
21
+ check();
22
+ // pane run acknowledges command submission with an empty stdout on current Herdr.
23
+ if (!output.trim() && args[1] === 'run')
24
+ return {};
25
+ return JSON.parse(output).result;
26
+ };
27
+ const caller = (await call(['pane', 'current', '--current']))?.pane;
28
+ if (!caller?.pane_id || !caller.tab_id || !caller.workspace_id)
29
+ throw new Error('Missing current Herdr pane identity');
30
+ const label = 'Intercom monitor';
31
+ const legacyMarker = `Intercom monitor [${options.sessionId}]`;
32
+ const key = createHash('sha256').update(options.sessionId).digest('hex').slice(0, 24);
33
+ const directory = path.join(options.root, '.pi-intercom');
34
+ await mkdir(directory, { recursive: true });
35
+ const file = path.join(directory, `monitor-${key}.json`), lock = `${file}.lock`;
36
+ check();
37
+ try {
38
+ await writeFile(lock, '', { flag: 'wx', mode: 0o600 });
39
+ }
40
+ catch {
41
+ throw new Error(`Monitor launch locked; inspect ${lock} before recovery. No pane created.`);
42
+ }
43
+ let state;
44
+ const save = async () => {
45
+ const temporary = `${file}.${randomUUID()}.tmp`;
46
+ try {
47
+ await writeFile(temporary, JSON.stringify(state), { flag: 'wx', mode: 0o600 });
48
+ await rename(temporary, file);
49
+ }
50
+ finally {
51
+ await unlink(temporary).catch(() => { });
52
+ }
53
+ };
54
+ try {
55
+ try {
56
+ state = JSON.parse(await readFile(file, 'utf8'));
57
+ if (state?.version !== 1 || state.sessionId !== options.sessionId || typeof state.stage !== 'string')
58
+ throw new Error('Invalid monitor ownership record');
59
+ }
60
+ catch (e) {
61
+ if (e.code !== 'ENOENT')
62
+ throw e;
63
+ }
64
+ const panes = (await call(['pane', 'list', '--workspace', caller.workspace_id]))?.panes;
65
+ if (!Array.isArray(panes) || panes.some(p => !p?.pane_id || !p.tab_id))
66
+ throw new Error('Invalid Herdr pane inventory');
67
+ const matches = panes.filter(p => p.label === legacyMarker ||
68
+ (state && state.workspace === caller.workspace_id && state.pane === p.pane_id));
69
+ if (matches.length > 1)
70
+ throw new Error('Multiple monitor markers; inspect panes. No automatic changes.');
71
+ if (matches.length === 1) {
72
+ const pane = matches[0];
73
+ if (pane.pane_id === caller.pane_id || pane.tab_id !== caller.tab_id)
74
+ throw new Error('Monitor marker is on caller or moved to another tab; no duplicate created');
75
+ if (state && (state.workspace !== caller.workspace_id || state.caller !== caller.pane_id || (state.pane && state.pane !== pane.pane_id))) {
76
+ throw new Error('Monitor ownership differs from current caller; no automatic changes');
77
+ }
78
+ if (state && !['submitted', 'observed'].includes(state.stage)) {
79
+ throw new Error(`Monitor partial launch needs inspection (${state.stage}); no retry or duplicate`);
80
+ }
81
+ if (!state) {
82
+ state = { version: 1, sessionId: options.sessionId, workspace: caller.workspace_id, caller: caller.pane_id, pane: pane.pane_id, stage: 'observed' };
83
+ await save();
84
+ }
85
+ if (pane.label !== label)
86
+ await call(['pane', 'rename', pane.pane_id, label]);
87
+ return { outcome: 'existing pane preserved; process readiness not checked', pane: pane.pane_id };
88
+ }
89
+ if (panes.some(p => p.tab_id === caller.tab_id && p.label === label)) {
90
+ throw new Error('Unowned monitor label in coordinator tab; inspect before creating another pane');
91
+ }
92
+ if (state && (!['submitted', 'observed'].includes(state.stage) || state.workspace !== caller.workspace_id || panes.some(p => p.pane_id === state.pane))) {
93
+ throw new Error(`Monitor ownership/partial launch needs inspection (${state.stage}, pane ${state.pane ?? 'unknown'}); no retry or duplicate`);
94
+ }
95
+ await access(options.script); // Missing build must not leave a new empty pane.
96
+ check();
97
+ state = { version: 1, sessionId: options.sessionId, workspace: caller.workspace_id, caller: caller.pane_id, stage: 'split_pending' };
98
+ await save();
99
+ // Small top quarter after swapping pane identities. Verify actual geometry below.
100
+ const split = await call(['pane', 'split', '--pane', caller.pane_id, '--direction', 'down', '--ratio', '0.25', '--cwd', options.root, '--no-focus']);
101
+ const pane = split?.pane?.pane_id;
102
+ if (typeof pane !== 'string' || pane === caller.pane_id)
103
+ throw new Error('Split returned no distinct pane ID');
104
+ state.pane = pane;
105
+ state.stage = 'rename_pending';
106
+ await save();
107
+ await call(['pane', 'rename', pane, label]);
108
+ state.stage = 'swap_pending';
109
+ await save();
110
+ const swapped = await call(['pane', 'swap', '--source-pane', caller.pane_id, '--target-pane', pane]);
111
+ if (swapped?.swap?.changed !== true)
112
+ throw new Error('Herdr did not confirm pane swap');
113
+ const layout = (await call(['pane', 'layout', '--pane', caller.pane_id]))?.layout;
114
+ const top = layout?.panes?.find((p) => p.pane_id === pane)?.rect;
115
+ const bottom = layout?.panes?.find((p) => p.pane_id === caller.pane_id)?.rect;
116
+ if (!top || !bottom || top.y >= bottom.y || layout.focused_pane_id !== caller.pane_id)
117
+ throw new Error('Monitor geometry/focus not confirmed; inspect panes before recovery');
118
+ state.stage = 'run_pending';
119
+ await save();
120
+ const args = [options.script, '--root', options.root];
121
+ const command = platform === 'win32'
122
+ ? `powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded(`& ${psQuote(options.node ?? process.execPath)} ${args.map(psQuote).join(' ')}`)}`
123
+ : `sh -c ${shQuote(`exec ${shQuote(options.node ?? process.execPath)} ${args.map(shQuote).join(' ')}`)}`;
124
+ await call(['pane', 'run', pane, command]);
125
+ state.stage = 'submitted';
126
+ await save();
127
+ return { outcome: 'command submitted; monitor readiness not awaited', pane };
128
+ }
129
+ catch (e) {
130
+ throw new Error(`Intercom monitor: ${String(e)}. Pane ${state?.pane ?? 'unknown'} may remain; no automatic cleanup.`);
131
+ }
132
+ finally {
133
+ await unlink(lock).catch(() => { });
134
+ }
135
+ }
136
+ //# sourceMappingURL=monitor-launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-launcher.js","sourceRoot":"","sources":["../src/monitor-launcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ObservationSnapshot } from './snapshot.js';
2
+ /** Presentation only. All user-controlled text is sanitized before trusted ANSI styling. */
3
+ export declare function renderMonitor(snapshot: ObservationSnapshot | undefined, width: number, height: number, now?: number, color?: boolean, options?: {
4
+ selectedIndex?: number;
5
+ details?: boolean;
6
+ }): string[];
@@ -0,0 +1,134 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+ import { truncateToWidth, visibleWidth } from 'pi-intercom-tui';
3
+ import { workerObservation } from './worker-status.js';
4
+ const safe = (value) => stripVTControlCharacters(value).replace(/[\x00-\x1f\x7f-\x9f\u2028-\u202e\u2066-\u2069]/g, ' ');
5
+ const clip = (value, width) => stripVTControlCharacters(truncateToWidth(safe(value), Math.max(0, width)));
6
+ const cell = (value, width) => {
7
+ const text = clip(value, width);
8
+ return text + ' '.repeat(Math.max(0, width - visibleWidth(text)));
9
+ };
10
+ const ageLabel = (milliseconds) => {
11
+ const seconds = Math.floor(milliseconds / 1000);
12
+ if (seconds < 60)
13
+ return `${seconds}s ago`;
14
+ if (seconds < 3600)
15
+ return `${Math.floor(seconds / 60)}m ago`;
16
+ if (seconds < 86400)
17
+ return `${Math.floor(seconds / 3600)}h ago`;
18
+ return `${Math.floor(seconds / 86400)}d ago`;
19
+ };
20
+ const reportLabels = {
21
+ blocked: 'Blocked', needs_decision: 'Needs decision', ready_for_review: 'Ready for review',
22
+ };
23
+ // Wrap only sanitized grapheme clusters, bounded by the terminal's remaining rows.
24
+ function wrapSummary(value, width, rows) {
25
+ if (width <= 0 || rows <= 0)
26
+ return [];
27
+ const result = [];
28
+ let current = '';
29
+ for (const { segment } of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(safe(value))) {
30
+ if (visibleWidth(current + segment) > width) {
31
+ if (current)
32
+ result.push(current);
33
+ current = '';
34
+ if (result.length >= rows)
35
+ break;
36
+ }
37
+ current += visibleWidth(segment) <= width ? segment : clip(segment, width);
38
+ }
39
+ if (current && result.length < rows)
40
+ result.push(current);
41
+ return result;
42
+ }
43
+ /** Presentation only. All user-controlled text is sanitized before trusted ANSI styling. */
44
+ export function renderMonitor(snapshot, width, height, now = Date.now(), color = false, options = {}) {
45
+ width = Math.max(0, Math.min(1000, Math.floor(width)));
46
+ height = Math.max(0, Math.min(1000, Math.floor(height)));
47
+ if (!width || !height)
48
+ return [];
49
+ const paint = (code, text) => color ? `\x1b[${code}m${text}\x1b[0m` : text;
50
+ const dim = (text) => paint('90', text);
51
+ const line = (text) => clip(text, width);
52
+ const lines = [];
53
+ if (!snapshot?.config) {
54
+ lines.push(paint('33', line(' Status unavailable — waiting for local observations')));
55
+ }
56
+ else {
57
+ const workers = snapshot.config.agents.filter(agent => !agent.coordinator);
58
+ // Report remains distinct from observed status; last activity gives way first.
59
+ const wide = width >= 100;
60
+ const nameWidth = wide ? Math.min(22, Math.max(10, width - 88)) : 10;
61
+ const activityWidth = wide ? 12 : 8, ageWidth = 16, reportWidth = 16;
62
+ const table = (name, activity, age, detail, report = '—', tone = '90', selected = false) => {
63
+ const prefix = `${selected ? '› ' : ' '}${cell(name, nameWidth)} ${cell(age, ageWidth)} `;
64
+ const status = cell(activity, activityWidth);
65
+ const tail = ` ${cell(report, reportWidth)}${wide ? ` ${detail}` : ''}`;
66
+ // Clip as plain text first; no untrusted escape sequences enter the output.
67
+ if (!color)
68
+ return line(prefix + status + tail);
69
+ const namePart = clip(prefix, width);
70
+ const statusRoom = Math.max(0, width - visibleWidth(namePart));
71
+ const statusPart = clip(status, statusRoom);
72
+ const tailRoom = Math.max(0, statusRoom - visibleWidth(statusPart));
73
+ return (selected ? paint('1;36', namePart) : namePart) + paint(tone, statusPart) + dim(clip(tail, tailRoom));
74
+ };
75
+ lines.push(paint('1', table('Worker', 'Status', 'Seen', 'Last activity', 'Report')));
76
+ lines.push(dim(line(' ' + '─'.repeat(Math.max(0, width - 2)))));
77
+ const selectedIndex = Math.min(workers.length - 1, Math.max(-1, options.selectedIndex ?? -1));
78
+ const showDetails = Boolean(options.details && selectedIndex >= 0 && height >= 8);
79
+ const selectedReport = snapshot.reports?.find(report => report.sessionId === workers[selectedIndex]?.sessionId && reportLabels[report.status]);
80
+ const detailBudget = showDetails ? Math.min(selectedReport ? 12 : 5, height - 5) : 0;
81
+ const available = Math.max(0, height - lines.length - 2 - detailBudget);
82
+ const needsOverflow = workers.length > available;
83
+ const count = Math.min(workers.length, Math.max(0, available - (needsOverflow && available > 1 ? 1 : 0)));
84
+ const start = Math.max(0, Math.min(Math.max(0, workers.length - count), selectedIndex - count + 1));
85
+ let selectedDetail = [];
86
+ for (const [offset, worker] of workers.slice(start, start + count).entries()) {
87
+ const observation = workerObservation(snapshot, worker.sessionId, now);
88
+ const activity = observation.observedStatus, evidence = observation.lastActivity;
89
+ const age = observation.observationAgeSeconds === null ? '—' : ageLabel(observation.observationAgeSeconds * 1000) + (observation.stale ? ' (old)' : '');
90
+ const tone = observation.stale ? '90' : activity === 'thinking' ? '35' : activity === 'working' ? '36' : activity === 'idle' ? '32' : '90';
91
+ const selected = start + offset === selectedIndex;
92
+ const report = snapshot.reports?.find(report => report.sessionId === worker.sessionId && reportLabels[report.status]);
93
+ lines.push(table(worker.name, activity, age, evidence, report ? reportLabels[report.status] : '—', tone, selected));
94
+ if (selected && showDetails && report) {
95
+ const elapsed = now - Date.parse(report.updatedAt);
96
+ const reportAge = !Number.isFinite(elapsed) || elapsed < 0 ? 'clock uncertain' : ageLabel(elapsed) + (elapsed > snapshot.staleAfterMs ? ' (old)' : '');
97
+ const contextRows = detailBudget >= 5 ? [
98
+ ...(detailBudget >= 6 ? [paint('1', line(` ${worker.name}`))] : []),
99
+ line(` Responsibility: ${worker.description || 'Not specified'}`),
100
+ line(` Observed status: ${activity} · ${age}`),
101
+ ] : [];
102
+ selectedDetail = [
103
+ ...contextRows,
104
+ line(` Report: ${reportLabels[report.status]} · ${reportAge}`),
105
+ line(' Self-reported, pending review'),
106
+ ...wrapSummary(`Summary: ${report.summary}`, Math.max(0, width - 2), Math.max(0, detailBudget - 2 - contextRows.length)).map(text => line(` ${text}`)),
107
+ ];
108
+ }
109
+ else if (selected && showDetails)
110
+ selectedDetail = [
111
+ dim(line(' ' + '─'.repeat(Math.max(0, width - 2)))),
112
+ paint('1', line(` ${worker.name}`)),
113
+ line(` Responsibility: ${worker.description || 'Not specified'}`),
114
+ line(` Observed status: ${activity} · ${age}`),
115
+ line(` Last activity: ${evidence}`),
116
+ ];
117
+ }
118
+ if (workers.length > count && available > count)
119
+ lines.push(dim(line(` ${workers.length - count} more · rows ${count ? start + 1 : 0}–${start + count} of ${workers.length} · ↑↓ to browse`)));
120
+ lines.push(...(selectedReport ? selectedDetail.slice(0, detailBudget) : selectedDetail.slice(-detailBudget || selectedDetail.length)));
121
+ if (!workers.length && available)
122
+ lines.push(dim(line(' No workers configured yet')));
123
+ }
124
+ // Anchor the hint at the bottom; do not fill the pane with decorative boxes.
125
+ const partial = snapshot && (snapshot.truncated || snapshot.errors.length);
126
+ const controls = options.details ? 'q quit · Esc back · ↑↓ select' : 'q quit · ↑↓ select · Enter details';
127
+ const hasReports = snapshot?.reports?.some(report => reportLabels[report.status] && snapshot.config?.agents.some(agent => !agent.coordinator && agent.sessionId === report.sessionId));
128
+ const footer = width < 18 ? 'q quit' : ` ${controls}${partial ? ' · Partial observations' : ''}${hasReports ? ' · Reports: self-reported, pending review' : ''}`;
129
+ while (lines.length < height - 1)
130
+ lines.push('');
131
+ lines.push(dim(line(footer)));
132
+ return lines.slice(0, height);
133
+ }
134
+ //# sourceMappingURL=monitor-view.js.map