pi-better-background-tasks 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/shared-navigator.ts +45 -4
- package/src/tools.ts +7 -3
package/package.json
CHANGED
package/src/shared-navigator.ts
CHANGED
|
@@ -83,6 +83,7 @@ type NavigatorState = {
|
|
|
83
83
|
deps?: HostDeps;
|
|
84
84
|
lastHint?: string | null;
|
|
85
85
|
lastMainListLines?: string[];
|
|
86
|
+
lastMainListSignature?: string;
|
|
86
87
|
mainListWidgetInstalled?: boolean;
|
|
87
88
|
mainListRequestRender?: () => void;
|
|
88
89
|
mainListSelectedId?: string;
|
|
@@ -110,7 +111,6 @@ const MAIN_LIST_FALLBACK_WIDTH = 100;
|
|
|
110
111
|
const DETAIL_OVERLAY_FOOTER_ROWS = 3;
|
|
111
112
|
const EVIDENCE_SECTION_ID = "__evidence__";
|
|
112
113
|
const RUNNING_DOT_GLYPH = "●";
|
|
113
|
-
const RUNNING_DOT_FRAMES = ["dim", "accent", "accent", "dim"] as const;
|
|
114
114
|
|
|
115
115
|
function state(): NavigatorState {
|
|
116
116
|
const g = globalThis as typeof globalThis & { [GLOBAL_KEY]?: NavigatorState };
|
|
@@ -199,6 +199,7 @@ export function disposeBackgroundWorkNavigator(ctx?: ExtensionContext): void {
|
|
|
199
199
|
}
|
|
200
200
|
s.lastHint = undefined;
|
|
201
201
|
s.lastMainListLines = undefined;
|
|
202
|
+
s.lastMainListSignature = undefined;
|
|
202
203
|
s.mainListDeadlineScheduler?.dispose();
|
|
203
204
|
s.mainListDeadlineScheduler = undefined;
|
|
204
205
|
s.mainListWidgetInstalled = false;
|
|
@@ -263,6 +264,12 @@ function refreshMainListWidget(): void {
|
|
|
263
264
|
s.mainListDeadlineScheduler?.schedule(nextExpiry - now);
|
|
264
265
|
}
|
|
265
266
|
syncMainListSelection(rows);
|
|
267
|
+
const nextSignature = rows.length ? mainListSignature(rows, {
|
|
268
|
+
selectedId: s.mainListFocused ? s.mainListSelectedId : undefined,
|
|
269
|
+
focused: s.mainListFocused === true,
|
|
270
|
+
}) : undefined;
|
|
271
|
+
const changed = nextSignature !== s.lastMainListSignature;
|
|
272
|
+
s.lastMainListSignature = nextSignature;
|
|
266
273
|
s.lastMainListLines = rows.length ? buildMainListLines(rows, MAIN_LIST_FALLBACK_WIDTH, deps.truncate, themeFg(ctx), {
|
|
267
274
|
selectedId: s.mainListFocused ? s.mainListSelectedId : undefined,
|
|
268
275
|
focused: s.mainListFocused === true,
|
|
@@ -272,6 +279,7 @@ function refreshMainListWidget(): void {
|
|
|
272
279
|
try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
|
|
273
280
|
s.mainListWidgetInstalled = false;
|
|
274
281
|
s.mainListRequestRender = undefined;
|
|
282
|
+
s.lastMainListSignature = undefined;
|
|
275
283
|
return;
|
|
276
284
|
}
|
|
277
285
|
if (!s.mainListWidgetInstalled) {
|
|
@@ -280,7 +288,9 @@ function refreshMainListWidget(): void {
|
|
|
280
288
|
s.mainListWidgetInstalled = true;
|
|
281
289
|
} catch { /* ignore */ }
|
|
282
290
|
}
|
|
283
|
-
|
|
291
|
+
if (changed) {
|
|
292
|
+
try { s.mainListRequestRender?.(); } catch { /* ignore */ }
|
|
293
|
+
}
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
function themeFg(ctx: ExtensionContext): (color: string, value: string) => string {
|
|
@@ -481,9 +491,40 @@ function statusGlyph(row: InternalRow): string {
|
|
|
481
491
|
}
|
|
482
492
|
|
|
483
493
|
function statusIndicator(row: InternalRow, now = Date.now()): { glyph: string; color: string } {
|
|
494
|
+
void now;
|
|
484
495
|
if (row.statusTone !== "running") return { glyph: statusGlyph(row), color: toneColor(row.statusTone, row.status) };
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
return { glyph: RUNNING_DOT_GLYPH, color: "accent" };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function mainListSignature(rows: InternalRow[], options: { selectedId?: string; focused?: boolean } = {}): string {
|
|
500
|
+
return JSON.stringify({
|
|
501
|
+
focused: options.focused === true,
|
|
502
|
+
selectedId: options.focused === true ? options.selectedId ?? null : null,
|
|
503
|
+
rows: rows.map((row) => ({
|
|
504
|
+
navigatorId: row.navigatorId,
|
|
505
|
+
providerLabel: row.providerLabel,
|
|
506
|
+
parentRow: row.parentRow === true,
|
|
507
|
+
providerId: row.providerId,
|
|
508
|
+
id: row.id,
|
|
509
|
+
name: row.name ?? null,
|
|
510
|
+
model: row.model ?? null,
|
|
511
|
+
effort: row.effort ?? null,
|
|
512
|
+
tool: row.tool ?? null,
|
|
513
|
+
tokens: row.tokens ?? null,
|
|
514
|
+
command: row.command ?? null,
|
|
515
|
+
status: row.status,
|
|
516
|
+
statusTone: row.statusTone,
|
|
517
|
+
kind: row.kind,
|
|
518
|
+
primary: row.primary,
|
|
519
|
+
secondary: row.secondary ?? null,
|
|
520
|
+
facts: stableFacts(row.facts),
|
|
521
|
+
sortStartedAt: row.sortStartedAt,
|
|
522
|
+
})),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function stableFacts(facts: string[] | undefined): string[] {
|
|
527
|
+
return (facts ?? []).filter((fact) => !/\bleft$/.test(singleLine(fact)));
|
|
487
528
|
}
|
|
488
529
|
|
|
489
530
|
function rowSummary(row: InternalRow): string {
|
package/src/tools.ts
CHANGED
|
@@ -23,12 +23,16 @@ const SshSchema = Type.Object({
|
|
|
23
23
|
identity_file: Type.Optional(Type.String({ description: "SSH identity file path." })),
|
|
24
24
|
jump: Type.Optional(Type.String({ description: "SSH jump host passed with -J." })),
|
|
25
25
|
options: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Additional SSH -o key/value options. Agent-safe defaults remain enforced." })),
|
|
26
|
+
}, {
|
|
27
|
+
description: "Structured SSH connection for remote background tasks. Set ssh instead of wrapping command in a hand-written ssh command; command is the remote command, and Pi keeps durable local logs, status, callbacks, and remote stop semantics.",
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
const RemoteSchema = Type.Object({
|
|
29
31
|
session: Type.Optional(Type.Union([Type.Literal("tmux"), Type.Literal("direct")], { description: "Remote session mode. SSH spawn defaults to durable tmux. Watch always uses direct one-shot polls. Explicit direct spawn has weaker stop semantics." })),
|
|
30
32
|
install_tmux: Type.Optional(Type.Boolean({ description: "Allow SSH spawn to install tmux non-interactively when missing. Defaults true in tmux mode and is ignored for watch and direct spawn." })),
|
|
31
33
|
workdir: Type.Optional(Type.String({ description: "Remote working directory for the spawned command." })),
|
|
34
|
+
}, {
|
|
35
|
+
description: "Remote execution controls used with ssh. Omit session for SSH spawn to get the durable tmux default; SSH watch runs direct one-shot polls regardless of session and does not install tmux.",
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
const CommandFields = {
|
|
@@ -117,7 +121,7 @@ export function registerTools(pi: ExtensionAPI): void {
|
|
|
117
121
|
pi.registerTool({
|
|
118
122
|
name: "bg_task_spawn",
|
|
119
123
|
label: "BG Spawn",
|
|
120
|
-
description: "Start a long-running background process and return immediately with its task id. For remote work, prefer structured ssh: spawn defaults to a remote tmux session with durable local logs and real remote stop. If tmux is missing, the preset attempts to install tmux non-interactively and fails closed with operator guidance when setup cannot proceed. Explicit remote.session=direct skips tmux, but direct mode has weaker stop semantics and may leave the remote process running. Never wait or poll in the foreground.",
|
|
124
|
+
description: "Start a long-running background process and return immediately with its task id. For remote work, prefer structured ssh: pass ssh:{host,user} and put the remote command in command; spawn defaults to a remote tmux session with durable local logs and real remote stop. If tmux is missing, the preset attempts to install tmux non-interactively and fails closed with operator guidance when setup cannot proceed. Explicit remote.session=direct skips tmux, but direct mode has weaker stop semantics and may leave the remote process running. Never wait or poll in the foreground.",
|
|
121
125
|
parameters: SpawnParams,
|
|
122
126
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
123
127
|
activeSession = getCallbackOrigin(ctx);
|
|
@@ -130,7 +134,7 @@ export function registerTools(pi: ExtensionAPI): void {
|
|
|
130
134
|
pi.registerTool({
|
|
131
135
|
name: "bg_task_watch",
|
|
132
136
|
label: "BG Watch",
|
|
133
|
-
description: "Poll a command in the background until success_when, failure_when, or timeout matches. For remote work,
|
|
137
|
+
description: "Poll a command in the background until success_when, failure_when, or timeout matches. For remote work, prefer structured ssh: pass ssh:{host,user} and provide the remote command in command; each interval opens a direct one-shot SSH poll without tmux installation. Returns immediately with its task id. Default timeout 900 seconds; pass timeout_seconds:0 to disable.",
|
|
134
138
|
parameters: WatchParams,
|
|
135
139
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
136
140
|
activeSession = getCallbackOrigin(ctx);
|
|
@@ -190,7 +194,7 @@ export function registerTools(pi: ExtensionAPI): void {
|
|
|
190
194
|
pi.registerTool({
|
|
191
195
|
name: "bg_task",
|
|
192
196
|
label: "BG Task",
|
|
193
|
-
description: "Action wrapper for background tasks: spawn, watch, list, status, log, stop, or clear. For remote work,
|
|
197
|
+
description: "Action wrapper for background tasks: spawn, watch, list, status, log, stop, or clear. For remote work, prefer structured ssh: pass ssh:{host,user} and provide the remote command in command. SSH spawn defaults to durable tmux; SSH watches use direct one-shot polls without tmux installation; remote.session=direct is a weaker-stop spawn escape hatch. Spawn/watch return immediately; do not poll in foreground. For action:status, default compact output and use verbose:true only for full metadata. For action:log, default compact tail and use tail_lines:0 only for explicit full logs.",
|
|
194
198
|
parameters: ActionParams,
|
|
195
199
|
renderResult(result: unknown, options: unknown, theme: unknown) {
|
|
196
200
|
return renderBackgroundTaskLogDisplay(result, options, theme);
|