@mjasnikovs/pi-task 0.13.5 → 0.13.7
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/dist/remote/bridge.js +5 -4
- package/dist/remote/broadcast.d.ts +0 -1
- package/dist/remote/broadcast.js +0 -7
- package/dist/remote/events.js +5 -5
- package/dist/remote/push.d.ts +12 -3
- package/dist/remote/push.js +63 -9
- package/dist/remote/server.js +0 -16
- package/dist/remote/ui-script.d.ts +3 -0
- package/dist/remote/ui-script.js +804 -0
- package/dist/remote/ui-styles.d.ts +1 -0
- package/dist/remote/ui-styles.js +202 -0
- package/dist/remote/ui.js +4 -1000
- package/dist/shared/child-process.d.ts +27 -0
- package/dist/shared/child-process.js +151 -139
- package/dist/task/auto-orchestrator.js +3 -6
- package/dist/task/child-runner.js +1 -1
- package/dist/task/context-usage.d.ts +16 -0
- package/dist/task/context-usage.js +22 -0
- package/dist/task/external-context.d.ts +27 -0
- package/dist/task/external-context.js +93 -0
- package/dist/task/failure-classifier.js +1 -1
- package/dist/task/orchestrator.js +7 -13
- package/dist/task/parsers.d.ts +1 -15
- package/dist/task/parsers.js +17 -84
- package/dist/task/phases.d.ts +5 -7
- package/dist/task/phases.js +40 -84
- package/dist/task/prompts.d.ts +1 -0
- package/dist/task/prompts.js +9 -0
- package/dist/task/spec-validation.d.ts +23 -0
- package/dist/task/spec-validation.js +90 -0
- package/dist/task/widget.d.ts +1 -1
- package/dist/task/widget.js +1 -1
- package/dist/workers/pi-worker-docs.js +69 -58
- package/dist/workers/pi-worker-fetch.js +25 -21
- package/dist/workers/pi-worker-search.js +7 -13
- package/dist/workers/pi-worker.js +8 -14
- package/dist/workers/shared.d.ts +40 -0
- package/dist/workers/shared.js +31 -0
- package/package.json +1 -1
package/dist/remote/bridge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { broadcast as wsBroadcast
|
|
1
|
+
import { broadcast as wsBroadcast } from './broadcast.js';
|
|
2
2
|
import { pushNotify } from './push.js';
|
|
3
3
|
import { setPrompt, clearPrompt } from './session-state.js';
|
|
4
4
|
const g = globalThis;
|
|
@@ -56,9 +56,10 @@ export class SessionUI {
|
|
|
56
56
|
allowSkip: spec.allowSkip
|
|
57
57
|
};
|
|
58
58
|
setPrompt(prompt);
|
|
59
|
-
// Reaches a backgrounded/suspended phone, which the in-page UI can't.
|
|
60
|
-
if (
|
|
61
|
-
|
|
59
|
+
// Reaches a backgrounded/suspended phone, which the in-page UI can't. The
|
|
60
|
+
// service worker drops the banner if a window is visible+focused (sw.ts),
|
|
61
|
+
// so we always push and let delivery-time visibility decide.
|
|
62
|
+
void pushNotify('pi needs your input', spec.question, 'pi-prompt').catch(() => { });
|
|
62
63
|
// Local: resolves to a value/undefined, or undefined on abort. Swallow
|
|
63
64
|
// the rejection some implementations throw on abort so it never leaks.
|
|
64
65
|
const local = this.ctx.hasUI ?
|
|
@@ -3,4 +3,3 @@ export declare function addClient(ws: WebSocket): void;
|
|
|
3
3
|
export declare function removeClient(ws: WebSocket): void;
|
|
4
4
|
export declare function broadcast(msg: unknown): void;
|
|
5
5
|
export declare function sendTo(ws: WebSocket, msg: unknown): void;
|
|
6
|
-
export declare function hasConnectedClients(): boolean;
|
package/dist/remote/broadcast.js
CHANGED
package/dist/remote/events.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { setAgentIdle } from './state.js';
|
|
2
2
|
import { pushNotify } from './push.js';
|
|
3
3
|
import { publishNotify } from './bridge.js';
|
|
4
|
-
import { hasConnectedClients } from './broadcast.js';
|
|
5
4
|
import { agentStart, appendText, textEnd, startTool, updateTool, endTool, agentEnd, addUserTurn, addError, addSystemNote } from './session-state.js';
|
|
6
5
|
/** Mirror pi agent events into the authoritative SessionState. Each handler
|
|
7
6
|
* drives a mutator, which updates the snapshot AND broadcasts the live delta. */
|
|
@@ -23,8 +22,10 @@ export function setupEvents(pi) {
|
|
|
23
22
|
if (errorMessage || ae.reason === 'error') {
|
|
24
23
|
const message = errorMessage || 'Request failed';
|
|
25
24
|
addError(message);
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// Always push; the service worker suppresses the banner when a
|
|
26
|
+
// window is actually visible+focused (sw.ts), which is the only
|
|
27
|
+
// reliable foreground signal — an open WebSocket is not one.
|
|
28
|
+
void pushNotify('Agent error', message, 'pi-error').catch(() => { });
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
});
|
|
@@ -43,8 +44,7 @@ export function setupEvents(pi) {
|
|
|
43
44
|
pi.on('agent_end', (_event, ctx) => {
|
|
44
45
|
setAgentIdle(true);
|
|
45
46
|
agentEnd(ctx.getContextUsage());
|
|
46
|
-
|
|
47
|
-
void pushNotify('Task finished', '', 'pi-end').catch(() => { });
|
|
47
|
+
void pushNotify('Task finished', '', 'pi-end').catch(() => { });
|
|
48
48
|
});
|
|
49
49
|
pi.on('input', (event, _ctx) => {
|
|
50
50
|
if (event.source === 'interactive' && typeof event.text === 'string') {
|
package/dist/remote/push.d.ts
CHANGED
|
@@ -11,10 +11,16 @@ export interface VapidKeys {
|
|
|
11
11
|
publicKey: string;
|
|
12
12
|
privateKey: string;
|
|
13
13
|
}
|
|
14
|
-
/** Where the VAPID keypair is persisted
|
|
15
|
-
*
|
|
16
|
-
* losing these keys invalidates every existing browser subscription. */
|
|
14
|
+
/** Where the VAPID keypair is persisted — losing these keys invalidates every
|
|
15
|
+
* existing browser subscription. */
|
|
17
16
|
export declare function vapidStorePath(): string;
|
|
17
|
+
/** Where browser push subscriptions are mirrored to disk (next to vapid.json).
|
|
18
|
+
* Without this, a server restart — e.g. after a rebuild — silently drops every
|
|
19
|
+
* device: the in-memory store is empty, and a backgrounded/suspended PWA won't
|
|
20
|
+
* re-register until the user next foregrounds it, which is exactly when the push
|
|
21
|
+
* is no longer useful. The in-memory store stays authoritative within a process;
|
|
22
|
+
* this is its durable mirror. */
|
|
23
|
+
export declare function subscriptionsStorePath(): string;
|
|
18
24
|
/** Diagnostic log file. Defaults to /tmp for easy tailing; override with
|
|
19
25
|
* PI_REMOTE_PUSH_LOG. (The VAPID key stays in its durable XDG location.) */
|
|
20
26
|
export declare function pushLogPath(): string;
|
|
@@ -30,6 +36,9 @@ export declare function logPush(line: string): void;
|
|
|
30
36
|
/** Load the persisted VAPID keypair, generating and saving one on first use or
|
|
31
37
|
* if the stored file is missing/corrupt. Stable across restarts. */
|
|
32
38
|
export declare function loadOrCreateVapidKeys(file?: string): VapidKeys;
|
|
39
|
+
/** Load persisted subscriptions into the in-memory store. Best-effort: a missing
|
|
40
|
+
* or corrupt file just leaves the store as-is. Returns the resulting count. */
|
|
41
|
+
export declare function loadSubscriptions(file?: string): number;
|
|
33
42
|
export declare function addSubscription(sub: PushSubscriptionJSON): void;
|
|
34
43
|
export declare function removeSubscription(endpoint: string): void;
|
|
35
44
|
export declare function getSubscriptions(): PushSubscriptionJSON[];
|
package/dist/remote/push.js
CHANGED
|
@@ -2,12 +2,25 @@ import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import webpush from 'web-push';
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
/** Resolve the XDG data-home base. Under data-home (not cache) so the files it
|
|
6
|
+
* roots survive cache clears. Follows the repo's XDG convention; see
|
|
7
|
+
* workers/docs-core.ts. */
|
|
8
|
+
function dataHome() {
|
|
9
|
+
return process.env.XDG_DATA_HOME?.trim() || path.join(os.homedir(), '.local', 'share');
|
|
10
|
+
}
|
|
11
|
+
/** Where the VAPID keypair is persisted — losing these keys invalidates every
|
|
12
|
+
* existing browser subscription. */
|
|
8
13
|
export function vapidStorePath() {
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
return path.join(dataHome(), 'pi-task', 'vapid.json');
|
|
15
|
+
}
|
|
16
|
+
/** Where browser push subscriptions are mirrored to disk (next to vapid.json).
|
|
17
|
+
* Without this, a server restart — e.g. after a rebuild — silently drops every
|
|
18
|
+
* device: the in-memory store is empty, and a backgrounded/suspended PWA won't
|
|
19
|
+
* re-register until the user next foregrounds it, which is exactly when the push
|
|
20
|
+
* is no longer useful. The in-memory store stays authoritative within a process;
|
|
21
|
+
* this is its durable mirror. */
|
|
22
|
+
export function subscriptionsStorePath() {
|
|
23
|
+
return path.join(dataHome(), 'pi-task', 'subscriptions.json');
|
|
11
24
|
}
|
|
12
25
|
/** Diagnostic log file. Defaults to /tmp for easy tailing; override with
|
|
13
26
|
* PI_REMOTE_PUSH_LOG. (The VAPID key stays in its durable XDG location.) */
|
|
@@ -69,26 +82,67 @@ export function loadOrCreateVapidKeys(file = vapidStorePath()) {
|
|
|
69
82
|
return keys;
|
|
70
83
|
}
|
|
71
84
|
// In-memory subscription store, keyed by endpoint. Persisted on globalThis so it
|
|
72
|
-
// survives jiti re-evaluation on session switches (same pattern as broadcast.ts)
|
|
73
|
-
//
|
|
74
|
-
//
|
|
85
|
+
// survives jiti re-evaluation on session switches (same pattern as broadcast.ts),
|
|
86
|
+
// and mirrored to disk (subscriptionsStorePath) so it also survives a full
|
|
87
|
+
// process restart. A re-subscribe on page load can't be relied on for that: the
|
|
88
|
+
// devices that need server push are backgrounded/suspended and won't reload.
|
|
75
89
|
const g = globalThis;
|
|
90
|
+
const freshStore = !g.__piRemoteSubs;
|
|
76
91
|
if (!g.__piRemoteSubs)
|
|
77
92
|
g.__piRemoteSubs = new Map();
|
|
78
93
|
const subs = g.__piRemoteSubs;
|
|
94
|
+
// Hydrate once per process: a restarted server reloads the devices it knew so it
|
|
95
|
+
// can keep reaching them without waiting for each to re-register.
|
|
96
|
+
if (freshStore)
|
|
97
|
+
loadSubscriptions();
|
|
98
|
+
function isSubscription(x) {
|
|
99
|
+
return (typeof x === 'object'
|
|
100
|
+
&& x !== null
|
|
101
|
+
&& typeof x.endpoint === 'string');
|
|
102
|
+
}
|
|
103
|
+
/** Load persisted subscriptions into the in-memory store. Best-effort: a missing
|
|
104
|
+
* or corrupt file just leaves the store as-is. Returns the resulting count. */
|
|
105
|
+
export function loadSubscriptions(file = subscriptionsStorePath()) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
108
|
+
if (Array.isArray(parsed)) {
|
|
109
|
+
for (const s of parsed)
|
|
110
|
+
if (isSubscription(s))
|
|
111
|
+
subs.set(s.endpoint, s);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// missing or corrupt — keep whatever is already in memory
|
|
116
|
+
}
|
|
117
|
+
return subs.size;
|
|
118
|
+
}
|
|
119
|
+
/** Mirror the current store to disk. Best-effort; never throws, so a failed write
|
|
120
|
+
* can't break a subscribe request or a push. */
|
|
121
|
+
function saveSubscriptions(file = subscriptionsStorePath()) {
|
|
122
|
+
try {
|
|
123
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
124
|
+
writeFileSync(file, JSON.stringify([...subs.values()]), 'utf8');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// persistence is best-effort; the in-memory store remains authoritative
|
|
128
|
+
}
|
|
129
|
+
}
|
|
79
130
|
export function addSubscription(sub) {
|
|
80
131
|
if (!sub.endpoint)
|
|
81
132
|
return;
|
|
82
133
|
subs.set(sub.endpoint, sub);
|
|
134
|
+
saveSubscriptions();
|
|
83
135
|
}
|
|
84
136
|
export function removeSubscription(endpoint) {
|
|
85
|
-
subs.delete(endpoint)
|
|
137
|
+
if (subs.delete(endpoint))
|
|
138
|
+
saveSubscriptions();
|
|
86
139
|
}
|
|
87
140
|
export function getSubscriptions() {
|
|
88
141
|
return [...subs.values()];
|
|
89
142
|
}
|
|
90
143
|
export function clearSubscriptions() {
|
|
91
144
|
subs.clear();
|
|
145
|
+
saveSubscriptions();
|
|
92
146
|
}
|
|
93
147
|
/** True when the push service says the subscription is permanently gone and
|
|
94
148
|
* should be dropped (404 Not Found, 410 Gone). */
|
package/dist/remote/server.js
CHANGED
|
@@ -121,21 +121,6 @@ export async function startServer(onMessage, getHtml) {
|
|
|
121
121
|
handle.onFirstConnect = null;
|
|
122
122
|
// One authoritative snapshot — the client replaces its whole view with it.
|
|
123
123
|
sendTo(ws, snapshot());
|
|
124
|
-
// Heartbeat: ping every 30 s; terminate if the client doesn't respond.
|
|
125
|
-
// This catches phones that close/sleep without sending a TCP FIN, so the
|
|
126
|
-
// server's client-set stays accurate and push notifications fire correctly.
|
|
127
|
-
let alive = true;
|
|
128
|
-
const heartbeat = setInterval(() => {
|
|
129
|
-
if (!alive) {
|
|
130
|
-
ws.terminate();
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
alive = false;
|
|
134
|
-
ws.ping();
|
|
135
|
-
}, 10_000);
|
|
136
|
-
ws.on('pong', () => {
|
|
137
|
-
alive = true;
|
|
138
|
-
});
|
|
139
124
|
ws.on('message', data => {
|
|
140
125
|
let msg;
|
|
141
126
|
try {
|
|
@@ -157,7 +142,6 @@ export async function startServer(onMessage, getHtml) {
|
|
|
157
142
|
onMessage(msg.text);
|
|
158
143
|
});
|
|
159
144
|
ws.on('close', () => {
|
|
160
|
-
clearInterval(heartbeat);
|
|
161
145
|
removeClient(ws);
|
|
162
146
|
});
|
|
163
147
|
});
|