@saifymatteo/pi-extensions 0.0.0-stage → 0.1.0
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/LICENSE +661 -0
- package/NOTICE.md +20 -0
- package/README.md +151 -2
- package/inventory.json +73 -0
- package/package.json +74 -4
- package/packages/pi-bg-bash/LICENSE +661 -0
- package/packages/pi-bg-bash/NOTICE.md +3 -0
- package/packages/pi-bg-bash/README.md +41 -0
- package/packages/pi-bg-bash/examples/settings.example.json +6 -0
- package/packages/pi-bg-bash/package.json +38 -0
- package/packages/pi-bg-bash/src/index.ts +969 -0
- package/packages/pi-bg-bash/src/lib/async-work.ts +27 -0
- package/packages/pi-bg-bash/test/bg-bash.test.mjs +34 -0
- package/packages/pi-claude-usage/LICENSE +661 -0
- package/packages/pi-claude-usage/NOTICE.md +3 -0
- package/packages/pi-claude-usage/README.md +27 -0
- package/packages/pi-claude-usage/package.json +35 -0
- package/packages/pi-claude-usage/src/index.ts +130 -0
- package/packages/pi-clipboard-image/LICENSE +661 -0
- package/packages/pi-clipboard-image/NOTICE.md +3 -0
- package/packages/pi-clipboard-image/README.md +27 -0
- package/packages/pi-clipboard-image/package.json +36 -0
- package/packages/pi-clipboard-image/src/index.ts +527 -0
- package/packages/pi-codeblock-borders/LICENSE +661 -0
- package/packages/pi-codeblock-borders/NOTICE.md +3 -0
- package/packages/pi-codeblock-borders/README.md +30 -0
- package/packages/pi-codeblock-borders/package.json +35 -0
- package/packages/pi-codeblock-borders/src/index.ts +302 -0
- package/packages/pi-codegraph/LICENSE +661 -0
- package/packages/pi-codegraph/NOTICE.md +5 -0
- package/packages/pi-codegraph/README.md +35 -0
- package/packages/pi-codegraph/examples/codegraph.example.json +4 -0
- package/packages/pi-codegraph/package.json +37 -0
- package/packages/pi-codegraph/src/index.ts +573 -0
- package/packages/pi-codegraph/test/codegraph.test.mjs +21 -0
- package/packages/pi-compact-tools/LICENSE +661 -0
- package/packages/pi-compact-tools/NOTICE.md +3 -0
- package/packages/pi-compact-tools/README.md +27 -0
- package/packages/pi-compact-tools/package.json +36 -0
- package/packages/pi-compact-tools/src/index.ts +1161 -0
- package/packages/pi-decisions/LICENSE +661 -0
- package/packages/pi-decisions/NOTICE.md +3 -0
- package/packages/pi-decisions/README.md +37 -0
- package/packages/pi-decisions/examples/service-models.example.json +27 -0
- package/packages/pi-decisions/package.json +37 -0
- package/packages/pi-decisions/src/index.ts +364 -0
- package/packages/pi-decisions/src/lib/service-models.ts +95 -0
- package/packages/pi-decisions/test/service-models.test.mjs +71 -0
- package/packages/pi-load-pi-dotenv/LICENSE +661 -0
- package/packages/pi-load-pi-dotenv/NOTICE.md +3 -0
- package/packages/pi-load-pi-dotenv/README.md +27 -0
- package/packages/pi-load-pi-dotenv/package.json +32 -0
- package/packages/pi-load-pi-dotenv/src/index.ts +66 -0
- package/packages/pi-model-go/LICENSE +661 -0
- package/packages/pi-model-go/NOTICE.md +5 -0
- package/packages/pi-model-go/README.md +33 -0
- package/packages/pi-model-go/package.json +36 -0
- package/packages/pi-model-go/src/index.ts +171 -0
- package/packages/pi-model-go/src/lib/dialog.ts +31 -0
- package/packages/pi-model-go/src/lib/model-go.ts +239 -0
- package/packages/pi-model-go/test/behavior.test.mjs +17 -0
- package/packages/pi-prestart-working/LICENSE +661 -0
- package/packages/pi-prestart-working/NOTICE.md +3 -0
- package/packages/pi-prestart-working/README.md +25 -0
- package/packages/pi-prestart-working/package.json +35 -0
- package/packages/pi-prestart-working/src/index.ts +86 -0
- package/packages/pi-prestart-working/test/behavior.test.mjs +56 -0
- package/packages/pi-root-guard/LICENSE +661 -0
- package/packages/pi-root-guard/NOTICE.md +3 -0
- package/packages/pi-root-guard/README.md +27 -0
- package/packages/pi-root-guard/package.json +32 -0
- package/packages/pi-root-guard/src/index.ts +202 -0
- package/packages/pi-session-finder/LICENSE +661 -0
- package/packages/pi-session-finder/NOTICE.md +3 -0
- package/packages/pi-session-finder/README.md +33 -0
- package/packages/pi-session-finder/package.json +36 -0
- package/packages/pi-session-finder/src/index.ts +306 -0
- package/packages/pi-session-finder/src/lib/dialog.ts +31 -0
- package/packages/pi-session-finder/test/behavior.test.mjs +18 -0
- package/packages/pi-session-id/LICENSE +661 -0
- package/packages/pi-session-id/NOTICE.md +3 -0
- package/packages/pi-session-id/README.md +31 -0
- package/packages/pi-session-id/package.json +35 -0
- package/packages/pi-session-id/src/index.ts +20 -0
- package/packages/pi-session-id/test/behavior.test.mjs +25 -0
- package/packages/pi-session-nav/LICENSE +661 -0
- package/packages/pi-session-nav/NOTICE.md +3 -0
- package/packages/pi-session-nav/README.md +32 -0
- package/packages/pi-session-nav/package.json +36 -0
- package/packages/pi-session-nav/src/index.ts +203 -0
- package/packages/pi-session-nav/src/lib/dialog.ts +31 -0
- package/packages/pi-session-nav/test/behavior.test.mjs +36 -0
- package/packages/pi-session-rename/LICENSE +661 -0
- package/packages/pi-session-rename/NOTICE.md +3 -0
- package/packages/pi-session-rename/README.md +31 -0
- package/packages/pi-session-rename/package.json +35 -0
- package/packages/pi-session-rename/src/index.ts +456 -0
- package/packages/pi-session-rename/test/behavior.test.mjs +10 -0
- package/packages/pi-session-retention/LICENSE +661 -0
- package/packages/pi-session-retention/NOTICE.md +3 -0
- package/packages/pi-session-retention/README.md +62 -0
- package/packages/pi-session-retention/package.json +36 -0
- package/packages/pi-session-retention/src/README.md +42 -0
- package/packages/pi-session-retention/src/core.ts +333 -0
- package/packages/pi-session-retention/src/dialog.ts +118 -0
- package/packages/pi-session-retention/src/index.ts +311 -0
- package/packages/pi-session-retention/src/lib/dialog.ts +31 -0
- package/packages/pi-session-retention/src/safety.ts +50 -0
- package/packages/pi-session-retention/test/behavior.test.mjs +42 -0
- package/packages/pi-skill-usage-log/LICENSE +661 -0
- package/packages/pi-skill-usage-log/NOTICE.md +3 -0
- package/packages/pi-skill-usage-log/README.md +32 -0
- package/packages/pi-skill-usage-log/package.json +36 -0
- package/packages/pi-skill-usage-log/src/index.ts +316 -0
- package/packages/pi-skill-usage-log/src/lib/dialog.ts +31 -0
- package/packages/pi-skill-usage-log/src/lib/usage-log.ts +62 -0
- package/packages/pi-skill-usage-log/test/behavior.test.mjs +34 -0
- package/packages/pi-tool-usage-log/LICENSE +661 -0
- package/packages/pi-tool-usage-log/NOTICE.md +5 -0
- package/packages/pi-tool-usage-log/README.md +32 -0
- package/packages/pi-tool-usage-log/package.json +36 -0
- package/packages/pi-tool-usage-log/src/index.ts +233 -0
- package/packages/pi-tool-usage-log/src/lib/dialog.ts +31 -0
- package/packages/pi-tool-usage-log/src/lib/usage-log.ts +62 -0
- package/packages/pi-tool-usage-log/src/skill-usage-log.ts +316 -0
- package/packages/pi-tool-usage-log/test/behavior.test.mjs +23 -0
- package/packages/pi-tts/LICENSE +661 -0
- package/packages/pi-tts/NOTICE.md +3 -0
- package/packages/pi-tts/README.md +37 -0
- package/packages/pi-tts/examples/service-models.example.json +27 -0
- package/packages/pi-tts/package.json +37 -0
- package/packages/pi-tts/src/index.ts +135 -0
- package/packages/pi-tts/src/lib/service-models.ts +95 -0
- package/packages/pi-tts/test/tts-output.test.mjs +47 -0
- package/packages/pi-tts/test/tts.test.mjs +57 -0
- package/packages/pi-user-image-previews/LICENSE +661 -0
- package/packages/pi-user-image-previews/NOTICE.md +3 -0
- package/packages/pi-user-image-previews/README.md +27 -0
- package/packages/pi-user-image-previews/package.json +36 -0
- package/packages/pi-user-image-previews/src/index.ts +168 -0
- package/packages/pi-vision/LICENSE +661 -0
- package/packages/pi-vision/NOTICE.md +5 -0
- package/packages/pi-vision/README.md +43 -0
- package/packages/pi-vision/examples/vision-toolkit.example.json +12 -0
- package/packages/pi-vision/package.json +38 -0
- package/packages/pi-vision/skills/vision-tools/LICENSE +21 -0
- package/packages/pi-vision/skills/vision-tools/SKILL.md +134 -0
- package/packages/pi-vision/skills/vision-tools/agents/openai.yaml +4 -0
- package/packages/pi-vision/skills/vision-tools/bin/crop +94 -0
- package/packages/pi-vision/skills/vision-tools/bin/detect +18 -0
- package/packages/pi-vision/skills/vision-tools/bin/glance +98 -0
- package/packages/pi-vision/skills/vision-tools/bin/ground +18 -0
- package/packages/pi-vision/skills/vision-tools/bin/trace +134 -0
- package/packages/pi-vision/skills/vision-tools/bin/vision-doctor +168 -0
- package/packages/pi-vision/skills/vision-tools/detect.py +56 -0
- package/packages/pi-vision/skills/vision-tools/ground.py +216 -0
- package/packages/pi-vision/skills/vision-tools/references/gui.md +93 -0
- package/packages/pi-vision/skills/vision-tools/references/long-screenshot-ocr.md +103 -0
- package/packages/pi-vision/skills/vision-tools/references/restore-graphic.md +92 -0
- package/packages/pi-vision/skills/vision-tools/references/restore-structure.md +47 -0
- package/packages/pi-vision/skills/vision-tools/references/restore-ui.md +200 -0
- package/packages/pi-vision/skills/vision-tools/scripts/dominant_colors.py +224 -0
- package/packages/pi-vision/skills/vision-tools/scripts/extract_fg.py +278 -0
- package/packages/pi-vision/skills/vision-tools/scripts/html_shot.py +111 -0
- package/packages/pi-vision/skills/vision-tools/scripts/long_screenshot_ocr.py +1245 -0
- package/packages/pi-vision/skills/vision-tools/scripts/pixel_diff.py +88 -0
- package/packages/pi-vision/skills/vision-tools/vision_client.py +581 -0
- package/packages/pi-vision/src/index.ts +728 -0
- package/packages/pi-vision/test/vision.test.mjs +30 -0
- package/packages/pi-wait/LICENSE +661 -0
- package/packages/pi-wait/NOTICE.md +3 -0
- package/packages/pi-wait/README.md +37 -0
- package/packages/pi-wait/package.json +37 -0
- package/packages/pi-wait/src/index.ts +88 -0
- package/packages/pi-wait/src/lib/async-work.ts +27 -0
- package/packages/pi-wait/test/wait.test.mjs +43 -0
- package/packages/pi-zvec/LICENSE +661 -0
- package/packages/pi-zvec/NOTICE.md +3 -0
- package/packages/pi-zvec/README.md +37 -0
- package/packages/pi-zvec/examples/zvec.example.json +5 -0
- package/packages/pi-zvec/package.json +37 -0
- package/packages/pi-zvec/src/index.ts +754 -0
- package/packages/pi-zvec/test/zvec.test.mjs +19 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @saifymatteo/pi-wait
|
|
2
|
+
|
|
3
|
+
## Purpose and registrations
|
|
4
|
+
|
|
5
|
+
Registers the `wait` tool, joining 1..64 unique explicit `shell:<id>` / `agent:<id>` jobs. Returns results in requested order. Timeout releases claims without stopping work; abort/session changes invalidate the join. No commands or configuration files; no external CLI/model service.
|
|
6
|
+
|
|
7
|
+
## Cooperative delivery contract
|
|
8
|
+
|
|
9
|
+
Synchronous session-scoped `async-work:providers:v1` discovery receives `{sessionFile, add(provider)}`. Providers expose `source` and atomic `claim(ids, owner)` returning `{result, commit, release}`. Wait validates all results and serializes a snapshot before committing; finally it releases all leases. Commit/release must be synchronous, idempotent and nonthrowing. Duplicate providers, already consumed/submitted work and missing providers fail rather than duplicate delivery. See `src/lib/async-work.ts`.
|
|
10
|
+
|
|
11
|
+
Load `pi-bg-bash` for shell jobs. An orchestrator implementing the same contract is separate and external, not bundled or created by this package; it supplies agent jobs and parent-abort fleet cancellation.
|
|
12
|
+
|
|
13
|
+
## Host limitations and security
|
|
14
|
+
|
|
15
|
+
Requires Pi's shared synchronous event bus and session manager; does not join arbitrary OS processes or persist jobs across runtime restarts. No network/filesystem operations of its own. Provider job output becomes model-visible; do not join secret-bearing results. Timeout is 300000ms by default, maximum 1800000ms.
|
|
16
|
+
|
|
17
|
+
## Tests
|
|
18
|
+
|
|
19
|
+
From the monorepo root with its existing test dependencies available:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
node --test packages/pi-wait/test/*.test.mjs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Tests use the shared test-only TypeScript/VM loader, fixtures, and host/service stubs. They do not prove compatibility with a live host, CLI, or model service.
|
|
26
|
+
|
|
27
|
+
## Running locally
|
|
28
|
+
|
|
29
|
+
From the monorepo root:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pi -e ./packages/pi-wait
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Node >=24; host imports are peers, not bundled dependencies. Load only one copy: for an isolated session use `--no-extensions` with explicit `-e` paths so the original live extension is not also discovered. Set `PI_CODING_AGENT_DIR` to a disposable agent directory before launch; unset means the normal `~/.pi/agent` fallback. Extension config continues to resolve there, never relative to this package.
|
|
36
|
+
|
|
37
|
+
Examples are manually authored, sanitized reference documents, not runtime defaults. Review and manually create only the needed files in the isolated agent directory; do not copy live credentials or endpoints. No example is automatically loaded or copied. Loading an extension grants Pi-process OS permissions. Licensed AGPL-3.0-only; see [NOTICE.md](NOTICE.md).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saifymatteo/pi-wait",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"examples",
|
|
12
|
+
"README.md",
|
|
13
|
+
"NOTICE.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"pi": {
|
|
17
|
+
"extensions": [
|
|
18
|
+
"./src/index.ts"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
23
|
+
"typebox": "*"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=24"
|
|
27
|
+
},
|
|
28
|
+
"description": "Join background jobs (shell:/agent:) with lease-based delivery.",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/saifymatteo/pi-extensions.git",
|
|
35
|
+
"directory": "packages/pi-wait"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { ASYNC_WORK_PROVIDERS, type Lease, type Provider, type ProviderDiscovery, type WorkSource } from "./lib/async-work";
|
|
4
|
+
|
|
5
|
+
export default function (pi: ExtensionAPI) {
|
|
6
|
+
let sequence = 0;
|
|
7
|
+
pi.registerTool({
|
|
8
|
+
name: "wait",
|
|
9
|
+
label: "Wait",
|
|
10
|
+
description: "Join explicit background jobs (shell:1, agent:2). Returns all results together. Timeout releases delivery ownership without stopping jobs; Escape/parent abort retains fleet cancellation. Already consumed or externally submitted results cannot be joined.",
|
|
11
|
+
parameters: Type.Object({
|
|
12
|
+
jobs: Type.Array(Type.String({ pattern: "^(shell|agent):[1-9][0-9]*$" }), { minItems: 1, maxItems: 64, uniqueItems: true }),
|
|
13
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 1800000, default: 300000 })),
|
|
14
|
+
}),
|
|
15
|
+
promptGuidelines: ["After dispatch, do independent work first. When blocked on explicit jobs, use wait with their shell:<id>/agent:<id> references. Never sleep or poll to wait. Do not answer redundant messages for results already known individually."],
|
|
16
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
17
|
+
const { jobs, timeoutMs = 300000 } = params;
|
|
18
|
+
if (!Array.isArray(jobs) || jobs.length < 1 || jobs.length > 64 || new Set(jobs).size !== jobs.length)
|
|
19
|
+
throw new Error("wait requires 1..64 unique job references");
|
|
20
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 1800000)
|
|
21
|
+
throw new Error("timeoutMs must be an integer from 1 to 1800000");
|
|
22
|
+
const groups = new Map<WorkSource, number[]>();
|
|
23
|
+
for (const ref of jobs) {
|
|
24
|
+
const match = typeof ref === "string" && /^(shell|agent):([1-9][0-9]*)$/.exec(ref);
|
|
25
|
+
if (!match || !Number.isSafeInteger(Number(match[2]))) throw new Error(`Invalid job reference: ${ref}`);
|
|
26
|
+
const source = match[1] as WorkSource;
|
|
27
|
+
groups.set(source, [...(groups.get(source) ?? []), Number(match[2])]);
|
|
28
|
+
}
|
|
29
|
+
const providers = new Map<WorkSource, Provider>();
|
|
30
|
+
const duplicates = new Set<WorkSource>();
|
|
31
|
+
const discovery: ProviderDiscovery = {
|
|
32
|
+
sessionFile: ctx.sessionManager.getSessionFile(),
|
|
33
|
+
add(provider) {
|
|
34
|
+
if (providers.has(provider.source)) duplicates.add(provider.source);
|
|
35
|
+
providers.set(provider.source, provider);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
pi.events.emit(ASYNC_WORK_PROVIDERS, discovery);
|
|
39
|
+
const leases: Lease[] = [];
|
|
40
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
41
|
+
let onAbort: (() => void) | undefined;
|
|
42
|
+
const cancelled = "wait cancelled; delivery claims released (parent abort may cancel agents)";
|
|
43
|
+
const checkActive = () => {
|
|
44
|
+
if (signal?.aborted) throw new Error(cancelled);
|
|
45
|
+
if (ctx.sessionManager.getSessionFile() !== discovery.sessionFile)
|
|
46
|
+
throw new Error("wait session changed; delivery claims released");
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
checkActive();
|
|
50
|
+
const owner = `wait:${toolCallId}:${++sequence}`;
|
|
51
|
+
for (const [source, ids] of groups) {
|
|
52
|
+
if (duplicates.has(source)) throw new Error(`Multiple ${source} providers for this session`);
|
|
53
|
+
const provider = providers.get(source);
|
|
54
|
+
if (!provider) throw new Error(`No ${source} provider for this session`);
|
|
55
|
+
leases.push(provider.claim(ids, owner));
|
|
56
|
+
}
|
|
57
|
+
const interrupted = new Promise<never>((_resolve, reject) => {
|
|
58
|
+
onAbort = () => reject(new Error(cancelled));
|
|
59
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
60
|
+
if (signal?.aborted) onAbort();
|
|
61
|
+
timer = setTimeout(() => reject(new Error(`wait timed out after ${timeoutMs}ms; jobs continue running`)), timeoutMs);
|
|
62
|
+
});
|
|
63
|
+
const batches = await Promise.race([Promise.all(leases.map((lease) => lease.result)), interrupted]);
|
|
64
|
+
checkActive();
|
|
65
|
+
const all = batches.flat();
|
|
66
|
+
// Validate producer output before consuming any lease.
|
|
67
|
+
const results = jobs.map((ref) => {
|
|
68
|
+
const matches = all.filter((result) => `${result.source}:${result.id}` === ref);
|
|
69
|
+
if (matches.length !== 1 || !Number.isSafeInteger(matches[0].id) || typeof matches[0].content !== "string" ||
|
|
70
|
+
!matches[0].details || typeof matches[0].details !== "object" || Array.isArray(matches[0].details))
|
|
71
|
+
throw new Error(`Invalid provider result for ${ref}`);
|
|
72
|
+
return matches[0];
|
|
73
|
+
});
|
|
74
|
+
if (all.length !== jobs.length) throw new Error("Unexpected provider results");
|
|
75
|
+
// Materialize a JSON-safe snapshot before committing: producer metadata may be invalid.
|
|
76
|
+
const response = JSON.parse(JSON.stringify({ content: [{ type: "text", text:
|
|
77
|
+
results.map((result) => `[${result.source}:${result.id}]\n${result.content}`).join("\n\n") }], details: { results } }));
|
|
78
|
+
checkActive();
|
|
79
|
+
for (const lease of leases) lease.commit();
|
|
80
|
+
return response;
|
|
81
|
+
} finally {
|
|
82
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
83
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
84
|
+
for (const lease of leases) lease.release();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Synchronous, session-scoped discovery. Providers retain all execution state.
|
|
2
|
+
* claim must validate the whole group before changing ownership (or roll itself back).
|
|
3
|
+
* commit/release are synchronous, idempotent, and must not throw. commit consumes;
|
|
4
|
+
* release restores normal delivery without cancelling work. Never retract native queues.
|
|
5
|
+
*/
|
|
6
|
+
export const ASYNC_WORK_PROVIDERS = "async-work:providers:v1";
|
|
7
|
+
export type WorkSource = "shell" | "agent";
|
|
8
|
+
export interface WorkResult {
|
|
9
|
+
source: WorkSource;
|
|
10
|
+
id: number;
|
|
11
|
+
content: string;
|
|
12
|
+
details: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface Lease {
|
|
15
|
+
result: Promise<WorkResult[]>;
|
|
16
|
+
commit(): void;
|
|
17
|
+
release(): void;
|
|
18
|
+
}
|
|
19
|
+
export interface Provider {
|
|
20
|
+
source: WorkSource;
|
|
21
|
+
claim(ids: number[], owner: string): Lease;
|
|
22
|
+
}
|
|
23
|
+
export interface ProviderDiscovery {
|
|
24
|
+
sessionFile?: string;
|
|
25
|
+
/** Must be called synchronously: EventBus.emit returns void. */
|
|
26
|
+
add(provider: Provider): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { loadExtension } from '../../../test/load-extension.mjs';
|
|
5
|
+
|
|
6
|
+
function setup(providers) {
|
|
7
|
+
let tool;
|
|
8
|
+
const Type = new Proxy({}, { get: () => () => ({}) });
|
|
9
|
+
const { default: extension } = loadExtension(fileURLToPath(new URL('../src/index.ts', import.meta.url)), { typebox: { Type } });
|
|
10
|
+
extension({ registerTool: value => { tool = value; }, events: { emit: (name, discovery) => {
|
|
11
|
+
assert.equal(name, 'async-work:providers:v1');
|
|
12
|
+
for (const provider of providers) discovery.add(provider);
|
|
13
|
+
} } });
|
|
14
|
+
return params => tool.execute('test', params, undefined, undefined, { sessionManager: { getSessionFile: () => 'fixture-session' } });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('wait validates references before discovery', async () => {
|
|
18
|
+
const run = setup([]);
|
|
19
|
+
await assert.rejects(run({ jobs: ['shell:0'] }), /Invalid job reference/);
|
|
20
|
+
await assert.rejects(run({ jobs: ['shell:1', 'shell:1'] }), /unique job references/);
|
|
21
|
+
await assert.rejects(run({ jobs: ['shell:1'], timeoutMs: 0 }), /timeoutMs/);
|
|
22
|
+
await assert.rejects(run({ jobs: ['shell:1'] }), /No shell provider/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('wait joins in requested order and commits then releases all leases', async () => {
|
|
26
|
+
const actions = [];
|
|
27
|
+
const provider = source => ({ source, claim: ids => ({
|
|
28
|
+
result: Promise.resolve(ids.map(id => ({ source, id, content: `${source} output`, details: {} }))),
|
|
29
|
+
commit: () => actions.push(`commit:${source}`), release: () => actions.push(`release:${source}`),
|
|
30
|
+
}) });
|
|
31
|
+
const result = await setup([provider('shell'), provider('agent')])({ jobs: ['agent:2', 'shell:1'] });
|
|
32
|
+
assert.match(result.content[0].text, /^\[agent:2\][\s\S]*\[shell:1\]/);
|
|
33
|
+
assert.equal(actions.join(','), 'commit:agent,commit:shell,release:agent,release:shell');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('malformed provider output is released without consumption', async () => {
|
|
37
|
+
let committed = false;
|
|
38
|
+
let released = false;
|
|
39
|
+
const run = setup([{ source: 'shell', claim: () => ({ result: Promise.resolve([]), commit: () => { committed = true; }, release: () => { released = true; } }) }]);
|
|
40
|
+
await assert.rejects(run({ jobs: ['shell:1'] }), /Invalid provider result/);
|
|
41
|
+
assert.equal(committed, false);
|
|
42
|
+
assert.equal(released, true);
|
|
43
|
+
});
|