@mjasnikovs/pi-task 0.40.26 → 0.40.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/workers/docs-chunk.js +16 -5
- package/dist/workers/pi-worker-docs.d.ts +9 -9
- package/dist/workers/pi-worker-docs.js +9 -9
- package/dist/workers/pi-worker-fetch.d.ts +2 -0
- package/dist/workers/pi-worker-fetch.js +2 -0
- package/dist/workers/shared.d.ts +2 -2
- package/dist/workers/shared.js +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/@mjasnikovs/pi-task)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
[](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
|
|
12
|
-
[](#development)
|
|
13
13
|
[](./tsconfig.json)
|
|
14
14
|
|
|
15
15
|
</div>
|
|
@@ -148,9 +148,10 @@ Prompts use a **first-answer-wins race**: the same question shows in the local T
|
|
|
148
148
|
|
|
149
149
|
Tap the bell (◯ → ◉) in the remote header to get pushed a notification — even with the app backgrounded or the phone locked — when:
|
|
150
150
|
|
|
151
|
-
- a **grill / clarify question** needs answering (*"pi needs your input"*),
|
|
152
|
-
- a **task finishes** (*"Task finished"*)
|
|
153
|
-
|
|
151
|
+
- a **grill / clarify question** needs answering (*"pi needs your input"*), or
|
|
152
|
+
- a **task finishes** (*"Task finished"*).
|
|
153
|
+
|
|
154
|
+
Host agent errors are deliberately **not** pushed — most of them happen outside any task, and a push on every one is just noise.
|
|
154
155
|
|
|
155
156
|
Delivery is **server → push service → device** over the [Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) standard (service worker + VAPID), so it reaches a suspended device. It works on desktop browsers and on iOS home-screen PWAs.
|
|
156
157
|
|
|
@@ -259,7 +260,7 @@ them checked in.
|
|
|
259
260
|
|
|
260
261
|
```sh
|
|
261
262
|
bun install
|
|
262
|
-
bun run test #
|
|
263
|
+
bun run test # 4508 tests pass across 243 files (1 skip)
|
|
263
264
|
bun run lint # prettier + eslint + tsc --noEmit
|
|
264
265
|
bun run build # tsc → dist/
|
|
265
266
|
```
|
|
@@ -99,6 +99,11 @@ export function splitAtMatches(text, re) {
|
|
|
99
99
|
* possible hallucination. Only reachable on non-ASCII text past the chunk ceiling.
|
|
100
100
|
*/
|
|
101
101
|
export function sliceBytes(s, maxBytes) {
|
|
102
|
+
// A non-positive cap never shrinks the buffer, so the loop below runs forever.
|
|
103
|
+
// Guarded here rather than at the callers: the cap is usually computed, and the
|
|
104
|
+
// next caller to compute one must not have to rediscover this.
|
|
105
|
+
if (maxBytes <= 0)
|
|
106
|
+
return s ? [s] : [];
|
|
102
107
|
const out = [];
|
|
103
108
|
let buf = Buffer.from(s, 'utf8');
|
|
104
109
|
while (buf.length > maxBytes) {
|
|
@@ -134,13 +139,19 @@ export function headedSlices(header, body, maxBytes) {
|
|
|
134
139
|
const prefixed = `${header}\n${body}`;
|
|
135
140
|
if (Buffer.byteLength(prefixed, 'utf8') <= maxBytes)
|
|
136
141
|
return [prefixed];
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// the
|
|
142
|
+
// The header is a LABEL, and `chunkReadme` builds it from an unbounded heading line.
|
|
143
|
+
// Left whole it starves the body: a header two bytes short of the cap turned a 200 KB
|
|
144
|
+
// section into 100,000 two-byte chunks, each re-carrying the 8 KB header. Splitting the
|
|
145
|
+
// cap evenly is the one division that needs no tuning, and a cut label still names the
|
|
146
|
+
// source.
|
|
147
|
+
const head = sliceBytes(header, Math.floor(maxBytes / 2))[0] ?? '';
|
|
148
|
+
const room = maxBytes - Buffer.byteLength(`${head}\n`, 'utf8');
|
|
149
|
+
// Only a cap of a byte or two reaches this. Slice the prefixed string and accept the
|
|
150
|
+
// degenerate result: the body pieces carry no provenance — the very defect this
|
|
151
|
+
// function exists to fix, but the alternative here is emitting nothing.
|
|
141
152
|
if (room <= 0)
|
|
142
153
|
return sliceBytes(prefixed, maxBytes);
|
|
143
|
-
return sliceBytes(body, room).map(slice => `${
|
|
154
|
+
return sliceBytes(body, room).map(slice => `${head}\n${slice}`);
|
|
144
155
|
}
|
|
145
156
|
/**
|
|
146
157
|
* Chunk a declaration file, one chunk per declaration, each labelled with the
|
|
@@ -76,15 +76,6 @@ export interface PiWorkerDocsInternals {
|
|
|
76
76
|
npmVersionLookup?: typeof defaultNpmVersionLookup;
|
|
77
77
|
}
|
|
78
78
|
export declare function registerPiWorkerDocs(pi: ExtensionAPI, internals?: PiWorkerDocsInternals): void;
|
|
79
|
-
/**
|
|
80
|
-
* The cache rule for the docs channel, as a NAMED export rather than an anonymous
|
|
81
|
-
* property of an adapter literal.
|
|
82
|
-
*
|
|
83
|
-
* As a property of the adapter literal it would be reachable only through
|
|
84
|
-
* `registerTool → execute()`, so a test would have to retype the rule and would then
|
|
85
|
-
* assert against its own copy — green even after the shipped rule changed. Exported,
|
|
86
|
-
* the test imports the rule it is checking.
|
|
87
|
-
*/
|
|
88
79
|
/**
|
|
89
80
|
* Did the excerpt cite a word the source never wrote?
|
|
90
81
|
*
|
|
@@ -95,6 +86,15 @@ export declare function registerPiWorkerDocs(pi: ExtensionAPI, internals?: PiWor
|
|
|
95
86
|
export declare function excerptFabricated(check: {
|
|
96
87
|
absent: readonly string[];
|
|
97
88
|
} | undefined): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The cache rule for the docs channel, as a NAMED export rather than an anonymous
|
|
91
|
+
* property of an adapter literal.
|
|
92
|
+
*
|
|
93
|
+
* As a property of the adapter literal it would be reachable only through
|
|
94
|
+
* `registerTool → execute()`, so a test would have to retype the rule and would then
|
|
95
|
+
* assert against its own copy — green even after the shipped rule changed. Exported,
|
|
96
|
+
* the test imports the rule it is checking.
|
|
97
|
+
*/
|
|
98
98
|
export declare function docsCacheable(d: Pick<DocsDetails, 'typeOnly' | 'excerptVerified' | 'excerptFabricated' | 'abstained'>): boolean;
|
|
99
99
|
/** The docs cache key: a package's answer is per (module, question), with the question
|
|
100
100
|
* lowercased and its whitespace collapsed so phrasing variants share one entry. Returns
|
|
@@ -389,15 +389,6 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
389
389
|
cacheable: docsCacheable
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
|
-
/**
|
|
393
|
-
* The cache rule for the docs channel, as a NAMED export rather than an anonymous
|
|
394
|
-
* property of an adapter literal.
|
|
395
|
-
*
|
|
396
|
-
* As a property of the adapter literal it would be reachable only through
|
|
397
|
-
* `registerTool → execute()`, so a test would have to retype the rule and would then
|
|
398
|
-
* assert against its own copy — green even after the shipped rule changed. Exported,
|
|
399
|
-
* the test imports the rule it is checking.
|
|
400
|
-
*/
|
|
401
392
|
/**
|
|
402
393
|
* Did the excerpt cite a word the source never wrote?
|
|
403
394
|
*
|
|
@@ -408,6 +399,15 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
408
399
|
export function excerptFabricated(check) {
|
|
409
400
|
return check !== undefined && check.absent.length > 0;
|
|
410
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* The cache rule for the docs channel, as a NAMED export rather than an anonymous
|
|
404
|
+
* property of an adapter literal.
|
|
405
|
+
*
|
|
406
|
+
* As a property of the adapter literal it would be reachable only through
|
|
407
|
+
* `registerTool → execute()`, so a test would have to retype the rule and would then
|
|
408
|
+
* assert against its own copy — green even after the shipped rule changed. Exported,
|
|
409
|
+
* the test imports the rule it is checking.
|
|
410
|
+
*/
|
|
411
411
|
export function docsCacheable(d) {
|
|
412
412
|
// Answer QUALITY only. Whether there IS an answer is `WorkerOutcome.kind`, and
|
|
413
413
|
// `makeWorkerTool` has already refused an `unavailable` before reaching here —
|
|
@@ -61,6 +61,8 @@ export function registerPiWorkerFetch(pi, internals = {}) {
|
|
|
61
61
|
// in the TEXT, not only in details: details are for the harness, and the
|
|
62
62
|
// worker acts on what it reads.
|
|
63
63
|
const text = result.nextStep ? `${body}\n\n${result.nextStep}` : body;
|
|
64
|
+
// Named, not inferred: `workerAnswer<T>` reads T off the literal, so a
|
|
65
|
+
// field added here would never reach the declaration the cache rules Pick from.
|
|
64
66
|
return workerAnswer(text, {
|
|
65
67
|
childExitCode: 0,
|
|
66
68
|
answer: result.answer,
|
package/dist/workers/shared.d.ts
CHANGED
|
@@ -99,8 +99,8 @@ export interface WorkerToolSpec<TParams extends TSchema, TDetails> {
|
|
|
99
99
|
* the network fetch + child summariser. Return `null` to opt a particular call
|
|
100
100
|
* OUT of caching (e.g. a project-source `.` lookup, whose answer the working tree
|
|
101
101
|
* mutates within a run). Omit entirely and the tool is never cached. The stored
|
|
102
|
-
* key is namespaced by tool name — joined with a
|
|
103
|
-
*
|
|
102
|
+
* key is namespaced by tool name — joined with a NUL, a byte no tool name or key
|
|
103
|
+
* can contain, so no pair of them can collide by concatenation.
|
|
104
104
|
*/
|
|
105
105
|
cacheKey?(params: Static<TParams>): string | null;
|
|
106
106
|
/**
|
package/dist/workers/shared.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.28",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|