@sgtbeatdown/pi-tasks 0.1.8 → 0.1.10
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 +49 -20
- package/package.json +2 -2
- package/skills/wolfpack-pi-task-delegation/SKILL.md +6 -4
- package/src/configured-task-core.ts +63 -0
- package/src/extension.ts +112 -35
- package/src/in-memory-task-relay.ts +6 -5
- package/src/index.ts +6 -7
- package/src/task-core.ts +40 -17
- package/src/task-inbox.ts +18 -4
- package/src/task-protocol.ts +19 -1
- package/src/task-relay-conformance.ts +11 -1
- package/src/task-store.ts +178 -368
- package/src/volatile-task-session.ts +283 -0
- package/src/wolfpack-endpoint-view.ts +46 -0
- package/src/wolfpack-local-auth.ts +20 -0
- package/src/wolfpack-task-relay.ts +37 -341
- package/src/sqlite-database.ts +0 -68
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# pi-tasks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Endpoint-owned task coordination for Pi over Wolfpack's memory-only relay. **Both relay mail and endpoint task bookkeeping live in RAM.** Pi's existing session history is the historical record; there is no SQLite database, task-history directory, migration, disk outbox or startup replay.
|
|
4
|
+
|
|
5
|
+
The [relay control API](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary) defines the transport boundary.
|
|
4
6
|
|
|
5
7
|
## endpoint-owned relay
|
|
6
8
|
|
|
7
|
-
The default extension
|
|
9
|
+
The default extension requires a compatible Wolfpack memory-owned server. It negotiates `volatile-v1` at `POST /api/task-relay/volatile-v1`. No transport opt-in flag is needed. The old durable relay adapter is removed, not retained as a compatibility mode.
|
|
8
10
|
|
|
9
|
-
Set `WOLFPACK_SESSION_NAME` for
|
|
11
|
+
Set `WOLFPACK_SESSION_NAME` for each Pi process. `WOLFPACK_PORT` defaults to `18790`; `WOLFPACK_SESSION_NAME` resolves the active Pi process to its relay endpoint. Read `taskEndpoint` from structured session creation/status and pass its opaque `{ relay, id }` unchanged. Never derive it from a name, terminal output or broker ID.
|
|
10
12
|
|
|
11
13
|
### minimal valid v2 send envelope
|
|
12
14
|
|
|
@@ -20,53 +22,80 @@ Set `WOLFPACK_SESSION_NAME` for every Pi process. The adapter uses `WOLFPACK_POR
|
|
|
20
22
|
}
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
The `agent_task_send` schema is exactly `to`, `task`, and optional `timeoutMs`. Unsupported fields are rejected before
|
|
25
|
+
The `agent_task_send` schema is exactly `to`, `task`, and optional `timeoutMs`. Unsupported fields are rejected before admission: a pre-admission validation rejection creates no task. If an outcome is uncertain, idempotency remains necessary within the current lifetime; inspect the returned task ID rather than blindly creating another assignment.
|
|
26
|
+
|
|
27
|
+
## history versus live state
|
|
28
|
+
|
|
29
|
+
- Pi requests task-processing turns through its safe `deliverAs: "followUp"` queue. Pi session messages and tool calls/results preserve historical context. Non-waking receipts, parent ACKs and late-terminal facts are archived as `pi-tasks-event-record` custom entries before transport ACK, without waking the model. Received model-visible task events include the complete structured event in `pi-tasks-event` details, including results—not just a rendered summary.
|
|
30
|
+
- Active tasks, outbox intentions, individual ACK checkpoints, deduplication and delivery-blocked evidence are RAM-owned. Each endpoint has lower-only limits of16,384 records/32MiB encoded state. Admission fails before exceeding those bounds; they are not a process-RSS guarantee.
|
|
31
|
+
- `agent_task_send` confirms relay acceptance, **not execution**. `receiver_recorded` confirms receiver RAM receipt, **not durable persistence**. Pi insertion and wake acceptance are separate evidence, never proof of model execution.
|
|
32
|
+
- Session history is not an operational journal. A new process/lifecycle starts empty with a fresh endpoint/generation; it does not reconstruct tasks, replay envelopes, reuse old ACKs or reopen a worker gate from old history.
|
|
33
|
+
- A relay restart can lose even accepted mail. A Pi restart loses that endpoint's active task state. Prior outcomes can be unknown; do not label them delivered, failed or recovered without evidence.
|
|
34
|
+
|
|
35
|
+
## memory-owned lifecycle
|
|
36
|
+
|
|
37
|
+
`createConfiguredTaskCore()` owns its transport and `createTaskStore()` RAM state. Startup registers; polling runs every five seconds and at agent settlement. Shutdown fences new work, aborts requests, drains active calls and discards state. Late callbacks cannot mutate a successor lifetime.
|
|
38
|
+
|
|
39
|
+
A live endpoint detecting relay reset stops. `/task-relay-rebind` explains the loss boundary; only `/task-relay-rebind --accept-relay-loss` explicitly discards old state and binds a fresh scope. Automatic polling never invokes rebind. Session history remains untouched, but no historical task becomes active again.
|
|
40
|
+
|
|
41
|
+
`createVolatileTaskSession({ url, callerSession, store })` is the lower-level API for a caller-owned RAM store. Transport reconnect within that same lifetime retains pending immutable retries; `rebind()` clears the store, while `close()` stops transport and the caller then closes its state. Persistence/path options are rejected. Old database files are neither read nor deleted.
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
Full immutable content, including the original creation timestamp, is reused for retries. Destination-confirmed acceptance is required. Conflicting content, exhausted delivery and reset cannot become successful acceptance. Individual ACKs preserve sparse bigint cursors and cannot skip earlier pending mail. Bounds/deadlines apply through complete response bodies, including noncooperative injected transports.
|
|
44
|
+
|
|
45
|
+
## trusted Tailnet and optional authentication
|
|
46
|
+
|
|
47
|
+
The intended deployment trusts its Tailnet machines; Tailscale access control is the network boundary. Do not expose owner APIs through the public internet/Funnel or an untrusted proxy. No additional JWT or peer-signature scheme is required by the task protocol.
|
|
48
|
+
|
|
49
|
+
Existing global Wolfpack JWT enforcement remains **optional**. If the owner configures `WOLFPACK_JWT_SECRET`, the configured Pi factory supports it with short-lived tokens only for HTTP loopback. It never discovers/distributes secrets or automatically forwards them to arbitrary HTTPS URLs. HTTP401 reports `RELAY_AUTH_REQUIRED`, not an epoch reset.
|
|
50
|
+
|
|
51
|
+
Remote task endpoints must be locally resolved aliases. The paired CLI qualifies a selected remote endpoint through the local coordinator; remote lists expose explicit remote metadata. Failed qualification preserves any created session/ID but removes unsafe `taskEndpoint` output and reports `taskEndpointError`.
|
|
26
52
|
|
|
27
53
|
## delegation workflow
|
|
28
54
|
|
|
29
|
-
Configure
|
|
55
|
+
Configure `WOLFPACK_IMPLEMENTER_MODEL` and `WOLFPACK_REVIEWER_MODEL`; defaults are `openai-codex/gpt-5.6-terra` and `openai-codex/gpt-5.6-sol`. Explicit user or project choices override those defaults.
|
|
30
56
|
|
|
31
57
|
```bash
|
|
32
58
|
IMPLEMENTER_MODEL="${WOLFPACK_IMPLEMENTER_MODEL:-openai-codex/gpt-5.6-terra}"
|
|
33
59
|
REVIEWER_MODEL="${WOLFPACK_REVIEWER_MODEL:-openai-codex/gpt-5.6-sol}"
|
|
34
60
|
```
|
|
35
61
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4.
|
|
40
|
-
5.
|
|
62
|
+
1. Use one assignment mode: full-startup instructions, or an endpoint assignment—not both. For a task worker: `wolfpack agent spawn --project-dir /absolute/worktree --name <task-role> --model "$IMPLEMENTER_MODEL" --task-worker --readiness-timeout-ms 30000 --json` (or `"$REVIEWER_MODEL"`). This mode rejects prompts/plans and `--notify-parent`; put all instructions in `agent_task_send.task`.
|
|
63
|
+
2. Verify exact session ID/root/Pi harness and registered `taskEndpoint`. `TASK_WORKER_PREFLIGHT_FAILED` precedes creation. `TASK_WORKER_NOT_READY` retains `createdSession`/`cleanup`; inspect `unconfirmed` cleanup by exact ID before retry. Registration is not model execution.
|
|
64
|
+
3. Submit the assignment and continue useful work. When none remains, yield to structured follow-up. Do not poll `agent_task_status` or `agent_task_inbox` merely to wait; use `agent_task_wait` only when explicitly asked to block.
|
|
65
|
+
4. Use `agent_task_message` for questions, answers and information. The receiver's last action is `agent_task_done`, not completion prose.
|
|
66
|
+
5. Independently verify, then acknowledge the terminal task and deliberately retain or clean up the parent-owned role.
|
|
41
67
|
|
|
42
|
-
|
|
68
|
+
Endpoint assignments require terminal completion and one `agent_task_ack`; full-startup children have no task ID, so use explicit completion/block notification and parent verification instead. Close only sessions the parent spawned using `wolfpack kill <stable-session-id> --json`, then verify that ID absent from `wolfpack list --json`. Never use `wolfpack session send`, `/exit`, or `/quit` for cleanup.
|
|
43
69
|
|
|
44
70
|
## worker-only execution gate
|
|
45
71
|
|
|
46
|
-
|
|
72
|
+
The exact value `PI_TASK_WORKER=1` enables a fail-closed model tool gate. Before assignment only inbox/status/wait are allowed; other tools receive `PI_TASK_WORKER_ASSIGNMENT_REQUIRED`. Ordinary interactive Pi and explicit user shell commands are outside this gate.
|
|
47
73
|
|
|
48
|
-
`PI_TASK_WORKER=1` sessions are leaf roles.
|
|
74
|
+
`PI_TASK_WORKER=1` sessions are leaf roles. Generic role-orchestration guidance applies only to non-worker coordinators. The spawning coordinator owns each child's complete lifecycle.
|
|
49
75
|
|
|
50
|
-
|
|
76
|
+
Workers cannot call `agent_task_send`, `agent_task_cancel`, or `agent_task_ack` (`PI_TASK_WORKER_COORDINATION_FORBIDDEN`). `agent_task_message` must name their eligible incorporated assignment. A structured session event alone is insufficient: its task must also be active in this endpoint's current RAM state. Old session history never authorizes work after restart.
|
|
51
77
|
|
|
52
78
|
Preflighting `agent_task_done` marks that task as closing before sibling calls are preflighted. Ordinary tools remain blocked for a closing, pending-terminal, accepted-terminal, or `delivery_blocked` task; an idempotent `agent_task_done` retry for that same assigned task remains allowed. Another independently active assignment can still authorize work.
|
|
53
79
|
|
|
54
80
|
## terminal delivery and acknowledgment
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`agent_task_ack` is valid only for a terminal origin-owned task. Sequential, concurrent, and restart retries reuse one durable `task.parent_acknowledged` event and the same destination envelope identities while retrying pending physical delivery.
|
|
82
|
+
During the active lifetime `terminalDelivery` reports `not_submitted`, `pending`, `accepted`, or `delivery_blocked`. The blocked variant includes stable intent/envelope identities, origin endpoint, timestamp, and structured non-retryable relay error. Canonical task status is never changed to `delivery_blocked`. Retryable failures reuse the original intent; permanent failures are not rearmed. This evidence is lost on endpoint restart.
|
|
59
83
|
|
|
60
|
-
Report source
|
|
84
|
+
`agent_task_ack` is terminal-only and origin-owned. Sequential/concurrent retries in one lifetime reuse the logical event and envelope IDs. There is no restart recovery. Report changed source paths in `result.changedFiles`; artifacts are receiver-project-relative regular files, not the changed-file list.
|
|
61
85
|
|
|
62
86
|
```json
|
|
63
87
|
{ "result": { "changedFiles": ["src/extension.ts"] }, "artifacts": [{ "path": "verification/task-2.md" }] }
|
|
64
88
|
```
|
|
65
89
|
|
|
66
|
-
## development
|
|
90
|
+
## development and verification
|
|
67
91
|
|
|
68
92
|
```bash
|
|
69
93
|
bun install
|
|
70
94
|
bun test
|
|
71
95
|
bun run typecheck
|
|
96
|
+
PI_TASKS_WOLFPACK_SOURCE=/absolute/clean/wolfpack-checkout \
|
|
97
|
+
PI_TASKS_WOLFPACK_REVISION=<full-commit-id> \
|
|
98
|
+
bun test tests/volatile-real-worker.test.ts tests/volatile-extension-worker.test.ts
|
|
72
99
|
```
|
|
100
|
+
|
|
101
|
+
Wolfpack worker tests require its supported Bun runtime (currently stable>=1.4.2). Optional source-selected checks use private HTTP/workers, not installed services. They test same-lifetime retry/ACK, explicit reset/rebind, fresh endpoint state and history-only evidence. `createInMemoryTaskRelay` is a deterministic conformance fixture, never the default runtime transport. Physical two-machine qualification, final-path performance, release packaging and live activation remain separate gates.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sgtbeatdown/pi-tasks",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.10",
|
|
4
|
+
"description": "Memory-only endpoint-owned Pi agent delegation through Wolfpack relay v2.",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts"
|
|
@@ -5,7 +5,7 @@ description: Use when opening, selecting, delegating to, checking, or cleaning u
|
|
|
5
5
|
|
|
6
6
|
# wolfpack pi task delegation
|
|
7
7
|
|
|
8
|
-
use `wolfpack-tailnet-control` for session control. this skill covers endpoint-owned v2 task lifecycle, structured handoffs, and parent verification. The package default
|
|
8
|
+
use `wolfpack-tailnet-control` for session control. this skill covers endpoint-owned v2 task lifecycle, structured handoffs, and parent verification. The cutover package default uses the epoch-bound `volatile-v1` transport with the unchanged `pi-tasks/v2` task protocol. It requires a coordinated compatible Wolfpack server; an old server is refused, never silently adopted.
|
|
9
9
|
|
|
10
10
|
## choose one assignment mode
|
|
11
11
|
|
|
@@ -74,13 +74,15 @@ Assignment completion ends the assigned task, not the reusable role session. Whe
|
|
|
74
74
|
1. create a role session through the readiness path above, or select an existing role by stable ID and verify its structured liveness, canonical project, Pi harness, and registered `taskEndpoint`. Never use terminal output as readiness evidence.
|
|
75
75
|
2. call `agent_task_send` with the opaque endpoint and concise instructions in `task`. The tool returns after relay acceptance only, not Pi insertion or model execution. If submission reports a retryable delivery error after local persistence, use the structured task ID from the error and inspect it rather than creating an unrelated replacement.
|
|
76
76
|
3. after `agent_task_send`, continue useful independent coordinator work. When none remains, yield the current turn and let structured task follow-up wake the parent; do not poll `agent_task_status` or `agent_task_inbox` merely to wait. Reserve those tools for concrete progress evidence or recovery, and call `agent_task_wait` only when the user explicitly asks to block.
|
|
77
|
-
4. use `agent_task_message` for
|
|
77
|
+
4. use `agent_task_message` for `question`, `answer`, or `information` flow within the active endpoint lifetime. Do not use rendered text, terminal output, or logs as lifecycle evidence.
|
|
78
78
|
5. assignees call `agent_task_done` as their final action with the assigned task ID, terminal status, concise summary, and optional structured result, error, and artifact declarations. Report source modifications in `result.changedFiles`; artifacts are receiver-project-relative regular files for a parent to inspect, not changed-file lists: `{ "result": { "changedFiles": ["src/extension.ts"] }, "artifacts": [{ "path": "verification/task-2.md" }] }`. No prose completion afterward.
|
|
79
79
|
6. the parent independently verifies files, diff, tests, and artifacts. Then call `agent_task_ack({ taskId })` for that one terminal task and explicitly retain or close the spawned role session.
|
|
80
80
|
|
|
81
81
|
## v2 delivery and recovery
|
|
82
82
|
|
|
83
|
-
Wolfpack
|
|
83
|
+
Wolfpack is a memory-only, content-blind relay; each Pi endpoint keeps active task state, event order, retries and receipts in RAM. There is no SQLite task database. Relay acceptance is not execution; receiver receipt is not durable persistence. Model-visible `pi-tasks-event` entries include the complete structured event in session history; task wakes use Pi's safe `deliverAs: "followUp"` queue. Non-waking receipts, parent ACKs and late-terminal facts are archived as `pi-tasks-event-record` custom entries, not replayed. History is for inspection, never reconstruction/replay or worker authorization after restart. A relay restart can lose even accepted mail; a Pi restart loses its active task state and starts a fresh endpoint.
|
|
84
|
+
|
|
85
|
+
A live endpoint detecting relay reset requires explicit owner action. `/task-relay-rebind` explains the loss boundary; `/task-relay-rebind --accept-relay-loss` discards old RAM state and binds a fresh endpoint. Session history stays untouched, but historical tasks are not adopted. Never invoke rebind as an automatic retry, claim recovery or silently reassign possibly delivered work. Old database files are not read, migrated or deleted; no backward-compatible durable mode is supported.
|
|
84
86
|
|
|
85
87
|
Receiver terminal submission has one task-wide logical identity. Same-status retries reuse it; a conflicting terminal status fails closed. Canonical status remains origin-owned, while `terminalDelivery` separately exposes `not_submitted`, `pending`, `accepted`, or `delivery_blocked`. Parent acknowledgment is terminal-only and retries reuse one logical event and stable envelope identities.
|
|
86
88
|
|
|
@@ -93,4 +95,4 @@ Set `PI_TASK_WORKER=1` only for task-only workers. Before a valid structured ass
|
|
|
93
95
|
- do not steer or interrupt an active Pi turn with task context; use the adapter's follow-up queue.
|
|
94
96
|
- Do not copy plans, source contents, or transcripts into task context.
|
|
95
97
|
- do not derive or transform opaque endpoint IDs.
|
|
96
|
-
- do not promise JWT federation, artifact byte transfer, exactly-once model execution, or successor
|
|
98
|
+
- do not promise JWT federation, artifact byte transfer, exactly-once model execution, or migration of historical tasks to successor endpoints.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { TaskCore } from "./task-core";
|
|
2
|
+
import { TaskProtocolError } from "./task-protocol";
|
|
3
|
+
import { createTaskStore } from "./task-store";
|
|
4
|
+
import { createVolatileTaskSession } from "./volatile-task-session";
|
|
5
|
+
import { wolfpackLocalHeaders } from "./wolfpack-local-auth";
|
|
6
|
+
|
|
7
|
+
export interface OwnedTaskCore extends TaskCore {
|
|
8
|
+
/** Fence new work, abort requests, settle active calls, then discard RAM state. */
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface ConfiguredTaskCoreOptions {
|
|
12
|
+
readonly sessionName?: string;
|
|
13
|
+
readonly baseUrl?: string;
|
|
14
|
+
readonly fetch?: typeof fetch;
|
|
15
|
+
readonly requestTimeoutMs?: number;
|
|
16
|
+
/** Explicit operator acceptance of loss/unknown outcomes; never set on automatic retry. */
|
|
17
|
+
readonly rebind?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Normal extension transport. Never probe/fallback to the legacy durable relay. */
|
|
21
|
+
export async function createConfiguredTaskCore(options: ConfiguredTaskCoreOptions = {}, signal?: AbortSignal): Promise<OwnedTaskCore> {
|
|
22
|
+
const sessionName = options.sessionName ?? process.env.WOLFPACK_SESSION_NAME;
|
|
23
|
+
if (!sessionName?.trim()) throw new TaskProtocolError("RELAY_UNAVAILABLE", "WOLFPACK_SESSION_NAME is required for the Wolfpack task adapter");
|
|
24
|
+
const port = process.env.WOLFPACK_PORT ?? "18790";
|
|
25
|
+
if (options.baseUrl === undefined && (!/^[0-9]+$/.test(port) || Number(port) < 1 || Number(port) > 65535)) throw new TypeError("invalid WOLFPACK_PORT");
|
|
26
|
+
const base = new URL(options.baseUrl ?? `http://127.0.0.1:${port}`);
|
|
27
|
+
if (base.username || base.password || base.search || base.hash || base.pathname !== "/") throw new TypeError("relay base URL must be an origin");
|
|
28
|
+
const url = new URL("/api/task-relay/volatile-v1", base).href;
|
|
29
|
+
const store = createTaskStore();
|
|
30
|
+
let session: ReturnType<typeof createVolatileTaskSession> | undefined;
|
|
31
|
+
try {
|
|
32
|
+
const requestFetch = options.fetch ?? fetch;
|
|
33
|
+
const authenticated = Object.assign((input: Parameters<typeof fetch>[0], init?: RequestInit) => requestFetch(input, { ...init, headers: wolfpackLocalHeaders(url, init?.headers) }), { preconnect: requestFetch.preconnect }) as typeof fetch;
|
|
34
|
+
session = createVolatileTaskSession({ url, callerSession: sessionName, store, fetch: authenticated, ...(options.requestTimeoutMs !== undefined && { requestTimeoutMs: options.requestTimeoutMs }) });
|
|
35
|
+
const core = await (options.rebind ? session.rebind(signal) : session.connect(signal));
|
|
36
|
+
const active = new Set<Promise<unknown>>();
|
|
37
|
+
let closed = false, closing: Promise<void> | undefined;
|
|
38
|
+
const owned = { ...core } as OwnedTaskCore;
|
|
39
|
+
for (const [key, value] of Object.entries(core)) {
|
|
40
|
+
if (typeof value !== "function") continue;
|
|
41
|
+
Object.defineProperty(owned, key, { enumerable: true, value: (...args: unknown[]) => {
|
|
42
|
+
if (closed) throw new TaskProtocolError("RELAY_CLOSED", "task session is closed", { retryable: false });
|
|
43
|
+
// Core methods may call sibling methods through `this` (notably intent ACK).
|
|
44
|
+
// Keep their original receiver while the outer promise owns the full operation.
|
|
45
|
+
const result: unknown = value.apply(core, args);
|
|
46
|
+
if (!(result instanceof Promise)) return result;
|
|
47
|
+
active.add(result);
|
|
48
|
+
void result.then(() => active.delete(result), () => active.delete(result));
|
|
49
|
+
return result;
|
|
50
|
+
} });
|
|
51
|
+
}
|
|
52
|
+
owned.close = () => {
|
|
53
|
+
if (closing) return closing;
|
|
54
|
+
closed = true;
|
|
55
|
+
session!.close();
|
|
56
|
+
closing = Promise.allSettled([...active]).then(() => { store.close(); });
|
|
57
|
+
return closing;
|
|
58
|
+
};
|
|
59
|
+
return owned;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
session?.close(); store.close(); throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/extension.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { Type } from "typebox";
|
|
|
6
6
|
import { abortableSleep } from "./abortable-sleep";
|
|
7
7
|
import { deliverTaskInbox, incorporatedTaskEvents } from "./task-inbox";
|
|
8
8
|
import { TaskDeliveryEvidenceState, TaskDeliveryStage, TaskOutboxDeliveryError, TaskProtocolError } from "./task-protocol";
|
|
9
|
-
import {
|
|
9
|
+
import { createConfiguredTaskCore } from "./configured-task-core";
|
|
10
|
+
import type { OwnedTaskCore } from "./configured-task-core";
|
|
10
11
|
import type { SubmitIntentInput, SubmitIntentOutcome, TaskCore } from "./task-core";
|
|
11
12
|
import type { TaskEndpoint, TaskSnapshot } from "./task-protocol";
|
|
12
13
|
|
|
@@ -41,51 +42,87 @@ const DoneParams = Type.Object({
|
|
|
41
42
|
artifacts: Type.Optional(Type.Array(Type.Object({ path: Type.String({ minLength: 1, description: "artifact paths are receiver-project-relative regular files" }), mimeType: Type.Optional(Type.String()), description: Type.Optional(Type.String()) }))),
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
export type ConfiguredCoreFactory = (signal?: AbortSignal) => Promise<TaskCore
|
|
45
|
+
export type ConfiguredCoreFactory = (signal?: AbortSignal, rebind?: boolean) => Promise<TaskCore & Partial<Pick<OwnedTaskCore, "close">>>;
|
|
46
|
+
export interface ConfiguredCoreLoader extends ConfiguredCoreFactory {
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
const defaultCoreFactory: ConfiguredCoreFactory = (signal, rebind) => createConfiguredTaskCore({ rebind }, signal);
|
|
45
51
|
|
|
46
|
-
/**
|
|
47
|
-
export function createConfiguredCoreLoader(factory: ConfiguredCoreFactory):
|
|
48
|
-
let corePromise:
|
|
49
|
-
|
|
52
|
+
/** One lifecycle owns setup and its core. Late setup cannot resurrect a closed lifecycle. */
|
|
53
|
+
export function createConfiguredCoreLoader(factory: ConfiguredCoreFactory): ConfiguredCoreLoader {
|
|
54
|
+
let corePromise: ReturnType<ConfiguredCoreFactory> | undefined;
|
|
55
|
+
let controller = new AbortController();
|
|
56
|
+
let epoch = 0, closed = false;
|
|
57
|
+
let closing: Promise<void> = Promise.resolve();
|
|
58
|
+
const load: ConfiguredCoreLoader = async (signal, rebind) => {
|
|
59
|
+
if (closed) throw new TaskProtocolError("RELAY_CLOSED", "task lifecycle is closed", { retryable: false });
|
|
50
60
|
if (corePromise) return corePromise;
|
|
51
|
-
const
|
|
61
|
+
const current = epoch;
|
|
62
|
+
const lifetimeSignal = controller.signal;
|
|
63
|
+
const combined = signal ? AbortSignal.any([signal, lifetimeSignal]) : lifetimeSignal;
|
|
64
|
+
const candidate = Promise.resolve().then(async () => {
|
|
65
|
+
if (combined.aborted) throw new TaskProtocolError("ABORTED", "task setup was cancelled");
|
|
66
|
+
const value = await factory(combined, rebind);
|
|
67
|
+
if (current !== epoch || closed || combined.aborted) {
|
|
68
|
+
await value.close?.();
|
|
69
|
+
throw new TaskProtocolError("RELAY_CLOSED", "task lifecycle was replaced", { retryable: false });
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
});
|
|
52
73
|
corePromise = candidate;
|
|
53
|
-
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
try { return await candidate; }
|
|
75
|
+
catch (error) { if (corePromise === candidate) corePromise = undefined; throw error; }
|
|
76
|
+
};
|
|
77
|
+
load.start = async () => {
|
|
78
|
+
const current = epoch;
|
|
79
|
+
await closing;
|
|
80
|
+
if (current !== epoch) throw new TaskProtocolError("RELAY_CLOSED", "task lifecycle was stopped during startup", { retryable: false });
|
|
81
|
+
if (closed) { closed = false; controller = new AbortController(); }
|
|
59
82
|
};
|
|
83
|
+
load.close = () => {
|
|
84
|
+
closed = true; epoch++; controller.abort();
|
|
85
|
+
const prior = corePromise; corePromise = undefined;
|
|
86
|
+
const dispose = async () => {
|
|
87
|
+
let value: Awaited<ReturnType<ConfiguredCoreFactory>> | undefined;
|
|
88
|
+
try { value = await prior; } catch { /* Setup failure already owns cleanup. */ }
|
|
89
|
+
await value?.close?.();
|
|
90
|
+
};
|
|
91
|
+
closing = Promise.all([closing, dispose()]).then(() => undefined);
|
|
92
|
+
return closing;
|
|
93
|
+
};
|
|
94
|
+
return load;
|
|
60
95
|
}
|
|
61
96
|
|
|
62
|
-
/** Registers
|
|
63
|
-
export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefined = undefined, createCore: ConfiguredCoreFactory =
|
|
97
|
+
/** Registers endpoint-owned tools using the memory-owned Wolfpack transport by default. */
|
|
98
|
+
export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefined = undefined, createCore: ConfiguredCoreFactory = defaultCoreFactory): void {
|
|
64
99
|
let inboxContext: ExtensionContext | undefined;
|
|
65
100
|
let backgroundTimer: ReturnType<typeof setInterval> | undefined;
|
|
66
101
|
let lifecycleEpoch = 0;
|
|
67
102
|
const closingTaskIds = new Set<string>();
|
|
68
103
|
const workerGateEnabled = process.env.PI_TASK_WORKER === "1";
|
|
69
|
-
const
|
|
104
|
+
const ownedLoader = core === undefined ? createConfiguredCoreLoader(createCore) : undefined;
|
|
105
|
+
const configuredCore: ConfiguredCoreFactory = ownedLoader ?? (async (): Promise<TaskCore> => core!);
|
|
70
106
|
const refreshInbox = createSingleFlightInboxRefresh(async (signal) => {
|
|
71
107
|
const context = inboxContext;
|
|
108
|
+
const epoch = lifecycleEpoch;
|
|
109
|
+
const isCurrent = (): boolean => inboxContext === context && lifecycleEpoch === epoch;
|
|
72
110
|
if (!context) return undefined;
|
|
73
111
|
const activeCore = await configuredCore(signal);
|
|
74
|
-
if (
|
|
112
|
+
if (!isCurrent()) return undefined;
|
|
75
113
|
let outboxError: unknown;
|
|
76
114
|
try {
|
|
77
115
|
await activeCore.flushOutbox(signal);
|
|
78
116
|
} catch (error) {
|
|
79
117
|
outboxError = error;
|
|
80
118
|
}
|
|
81
|
-
if (
|
|
119
|
+
if (!isCurrent()) return undefined;
|
|
82
120
|
try {
|
|
83
121
|
await activeCore.evaluateTimeouts(signal);
|
|
84
122
|
} catch (error) {
|
|
85
123
|
outboxError ??= error;
|
|
86
124
|
}
|
|
87
|
-
if (
|
|
88
|
-
const isCurrent = (): boolean => inboxContext === context;
|
|
125
|
+
if (!isCurrent()) return undefined;
|
|
89
126
|
const guardedCore: TaskCore = {
|
|
90
127
|
...activeCore,
|
|
91
128
|
async receive(receiveSignal) {
|
|
@@ -159,6 +196,8 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
159
196
|
pi.on("session_start", async (_event, context) => {
|
|
160
197
|
closingTaskIds.clear();
|
|
161
198
|
const epoch = ++lifecycleEpoch;
|
|
199
|
+
await ownedLoader?.start();
|
|
200
|
+
if (epoch !== lifecycleEpoch) return;
|
|
162
201
|
inboxContext = context;
|
|
163
202
|
if (backgroundTimer) clearInterval(backgroundTimer);
|
|
164
203
|
backgroundTimer = setInterval(() => {
|
|
@@ -168,25 +207,58 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
168
207
|
await refreshLifecycle(context, epoch);
|
|
169
208
|
});
|
|
170
209
|
pi.on("agent_end", async (_event, context) => {
|
|
210
|
+
if (!inboxContext) return;
|
|
171
211
|
const epoch = lifecycleEpoch;
|
|
172
212
|
inboxContext = context;
|
|
173
213
|
await refreshLifecycle(context, epoch);
|
|
174
214
|
});
|
|
175
215
|
pi.on("agent_settled", async (_event, context) => {
|
|
216
|
+
if (!inboxContext) return;
|
|
176
217
|
const epoch = lifecycleEpoch;
|
|
177
218
|
inboxContext = context;
|
|
178
219
|
await refreshLifecycle(context, epoch);
|
|
179
220
|
});
|
|
180
|
-
pi.on("session_shutdown", () => {
|
|
221
|
+
pi.on("session_shutdown", async () => {
|
|
181
222
|
closingTaskIds.clear();
|
|
182
223
|
lifecycleEpoch += 1;
|
|
183
224
|
if (backgroundTimer) clearInterval(backgroundTimer);
|
|
184
225
|
backgroundTimer = undefined;
|
|
185
226
|
inboxContext = undefined;
|
|
227
|
+
await ownedLoader?.close();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (ownedLoader && createCore === defaultCoreFactory) pi.registerCommand("task-relay-rebind", {
|
|
231
|
+
description: "Accept relay loss, discard active task state and bind a fresh endpoint",
|
|
232
|
+
async handler(args, context) {
|
|
233
|
+
if (args.trim() !== "--accept-relay-loss") {
|
|
234
|
+
context.ui.notify("Relay restart can lose accepted mail. Rebind discards this lifetime's task state; Pi session history remains, but is never replayed. Run /task-relay-rebind --accept-relay-loss to continue with a fresh endpoint.", "warning");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const epoch = ++lifecycleEpoch;
|
|
238
|
+
inboxContext = undefined;
|
|
239
|
+
if (backgroundTimer) clearInterval(backgroundTimer);
|
|
240
|
+
backgroundTimer = undefined;
|
|
241
|
+
await ownedLoader.close();
|
|
242
|
+
if (epoch !== lifecycleEpoch) return;
|
|
243
|
+
try {
|
|
244
|
+
await ownedLoader.start();
|
|
245
|
+
if (epoch !== lifecycleEpoch) return;
|
|
246
|
+
await ownedLoader(undefined, true);
|
|
247
|
+
if (epoch !== lifecycleEpoch) return;
|
|
248
|
+
closingTaskIds.clear();
|
|
249
|
+
context.ui.notify("Fresh task endpoint bound with empty state. Prior tasks remain only in session history.", "info");
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (epoch === lifecycleEpoch) context.ui.notify(outboxFailureStatus(error), "error");
|
|
252
|
+
}
|
|
253
|
+
if (epoch !== lifecycleEpoch) return;
|
|
254
|
+
inboxContext = context;
|
|
255
|
+
backgroundTimer = setInterval(() => { if (inboxContext) void refreshLifecycle(inboxContext, epoch); }, BACKGROUND_POLL_MS);
|
|
256
|
+
await refreshLifecycle(context, epoch);
|
|
257
|
+
},
|
|
186
258
|
});
|
|
187
259
|
|
|
188
260
|
pi.registerTool({
|
|
189
|
-
name: "agent_task_send", label: "Send Agent Task", description: "
|
|
261
|
+
name: "agent_task_send", label: "Send Agent Task", description: "Create a task in this endpoint's RAM and submit its assignment; restart loses active state.", parameters: SendParams,
|
|
190
262
|
async execute(_id, params, signal) {
|
|
191
263
|
try {
|
|
192
264
|
const sent = await (await configuredCore(signal)).createTask({ target: params.to, task: params.task, timeoutMs: params.timeoutMs ?? DEFAULT_TASK_TIMEOUT_MS }, signal);
|
|
@@ -201,7 +273,7 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
201
273
|
try {
|
|
202
274
|
const activeCore = await configuredCore(signal);
|
|
203
275
|
const task = activeCore.getTask(params.taskId);
|
|
204
|
-
if (!task) return taskError(new
|
|
276
|
+
if (!task) return taskError(new TaskProtocolError("UNKNOWN_TASK", `unknown task: ${params.taskId}`, { retryable: false }));
|
|
205
277
|
const deliveryEvidence = taskDeliveryEvidence(task);
|
|
206
278
|
return toolResult({ ...task, deliveryEvidence }, `## task status\n- task: \`${task.taskId}\`\n- status: ${task.status}${receiverAssignment(activeCore, task, context)}${deliveryEvidenceText(deliveryEvidence)}`);
|
|
207
279
|
} catch (error) { return taskError(error); }
|
|
@@ -215,7 +287,7 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
215
287
|
for (;;) {
|
|
216
288
|
await refreshInbox(signal);
|
|
217
289
|
const task = (await configuredCore(signal)).getTask(params.taskId);
|
|
218
|
-
if (!task) return taskError(new
|
|
290
|
+
if (!task) return taskError(new TaskProtocolError("UNKNOWN_TASK", `unknown task: ${params.taskId}`, { retryable: false }));
|
|
219
291
|
if (terminal(task.status)) return toolResult(task, `## task status\n- task: \`${task.taskId}\`\n- status: ${task.status}`);
|
|
220
292
|
if (Date.now() >= deadline) return toolResult({ taskId: params.taskId, status: task.status }, `## task wait\n- task: \`${params.taskId}\`\n- status: ${task.status}\n- wait timed out`);
|
|
221
293
|
onUpdate?.({ content: [{ type: "text", text: `waiting for ${params.taskId}...` }], details: {} });
|
|
@@ -237,12 +309,12 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
237
309
|
renderResult(result, _options, theme) { return new Text(theme.fg("accent", text(result))); },
|
|
238
310
|
});
|
|
239
311
|
pi.registerTool({
|
|
240
|
-
name: "agent_task_message", label: "Message Agent Task", description: "
|
|
312
|
+
name: "agent_task_message", label: "Message Agent Task", description: "Record a message intent in this lifetime before relay submission.", parameters: MessageParams,
|
|
241
313
|
async execute(_id, params, signal) { try { await (await configuredCore(signal)).submitIntent({ taskId: params.taskId, type: `task.${params.type}`, payload: { message: params.message } }, signal); return toolResult({ taskId: params.taskId }, `## task ${params.type}\n- task: \`${params.taskId}\``); } catch (error) { return taskError(error); } },
|
|
242
314
|
renderResult(result, _options, theme) { return new Text(theme.fg("accent", text(result))); },
|
|
243
315
|
});
|
|
244
316
|
pi.registerTool({
|
|
245
|
-
name: "agent_task_cancel", label: "Cancel Agent Task", description: "
|
|
317
|
+
name: "agent_task_cancel", label: "Cancel Agent Task", description: "Record cancellation in this lifetime before relay submission.", parameters: TaskIdParams,
|
|
246
318
|
async execute(_id, params, signal) {
|
|
247
319
|
try {
|
|
248
320
|
const activeCore = await configuredCore(signal);
|
|
@@ -262,7 +334,7 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
262
334
|
renderResult(result, _options, theme) { return new Text(theme.fg("accent", text(result))); },
|
|
263
335
|
});
|
|
264
336
|
pi.registerTool({
|
|
265
|
-
name: "agent_task_done", label: "Complete Agent Task", description: "
|
|
337
|
+
name: "agent_task_done", label: "Complete Agent Task", description: "Record a terminal intent in this lifetime before relay submission.", parameters: DoneParams,
|
|
266
338
|
async execute(_id, params, signal) {
|
|
267
339
|
try {
|
|
268
340
|
const activeCore = await configuredCore(signal);
|
|
@@ -275,7 +347,7 @@ export function registerAgentTaskTools(pi: ExtensionAPI, core: TaskCore | undefi
|
|
|
275
347
|
return blockedDoneResult(activeCore, params.taskId, params.status, error) ?? taskError(error);
|
|
276
348
|
}
|
|
277
349
|
const task = activeCore.getTask(params.taskId);
|
|
278
|
-
if (!task) return taskError(new
|
|
350
|
+
if (!task) return taskError(new TaskProtocolError("UNKNOWN_TASK", `unknown task: ${params.taskId}`, { retryable: false }));
|
|
279
351
|
if (submission?.authority === "origin" && submission.canonicalEvent.type === "task.late_terminal") {
|
|
280
352
|
return {
|
|
281
353
|
...toolResult({ taskId: params.taskId, requestedStatus: params.status, observedCanonicalStatus: task.status, canonicalEvent: submission.canonicalEvent }, `## canonical late terminal recorded\n- task: \`${params.taskId}\`\n- requested status: ${params.status}\n- canonical status remains: ${task.status}\n- ${params.summary}`),
|
|
@@ -313,34 +385,34 @@ export default function piTasks(pi: ExtensionAPI): void {
|
|
|
313
385
|
}
|
|
314
386
|
|
|
315
387
|
interface TaskDeliveryEvidence {
|
|
316
|
-
readonly
|
|
388
|
+
readonly receiverReceipt: "not_confirmed" | "confirmed";
|
|
317
389
|
readonly piInsertion: "not_confirmed" | "blocked" | "confirmed";
|
|
318
390
|
readonly wakeAcceptance: "not_confirmed" | "pending" | "confirmed";
|
|
319
391
|
readonly modelExecution: "not_evidenced";
|
|
320
392
|
}
|
|
321
393
|
|
|
322
394
|
function taskDeliveryEvidence(task: TaskSnapshot): TaskDeliveryEvidence {
|
|
323
|
-
let
|
|
395
|
+
let receiverReceipt: TaskDeliveryEvidence["receiverReceipt"] = "not_confirmed";
|
|
324
396
|
let piInsertion: TaskDeliveryEvidence["piInsertion"] = "not_confirmed";
|
|
325
397
|
let wakeAcceptance: TaskDeliveryEvidence["wakeAcceptance"] = "not_confirmed";
|
|
326
398
|
const assignmentEventId = task.events.find((event) => event.type === "task.created")?.eventId;
|
|
327
399
|
for (const event of task.events) {
|
|
328
400
|
if (event.type !== "task.delivery_receipt" || event.payload.eventId !== assignmentEventId) continue;
|
|
329
|
-
if (event.payload.stage === TaskDeliveryStage.
|
|
401
|
+
if (event.payload.stage === TaskDeliveryStage.receiverRecorded) receiverReceipt = "confirmed";
|
|
330
402
|
if (event.payload.stage === TaskDeliveryStage.piInsertion && event.payload.state === TaskDeliveryEvidenceState.blocked) piInsertion = "blocked";
|
|
331
403
|
if (event.payload.stage === TaskDeliveryStage.piInserted || event.payload.stage === undefined) {
|
|
332
|
-
|
|
404
|
+
receiverReceipt = "confirmed";
|
|
333
405
|
piInsertion = "confirmed";
|
|
334
406
|
}
|
|
335
407
|
if (event.payload.stage === TaskDeliveryStage.wakeRequested) wakeAcceptance = "pending";
|
|
336
408
|
if (event.payload.stage === TaskDeliveryStage.wakeAccepted) wakeAcceptance = "confirmed";
|
|
337
409
|
}
|
|
338
|
-
return {
|
|
410
|
+
return { receiverReceipt, piInsertion, wakeAcceptance, modelExecution: "not_evidenced" };
|
|
339
411
|
}
|
|
340
412
|
|
|
341
413
|
function deliveryEvidenceText(evidence: TaskDeliveryEvidence): string {
|
|
342
414
|
const insertion = evidence.piInsertion === "blocked" ? "blocked; retryable" : evidence.piInsertion;
|
|
343
|
-
return `\n- receiver
|
|
415
|
+
return `\n- receiver receipt (RAM): ${evidence.receiverReceipt}\n- Pi insertion: ${insertion}\n- wake acceptance: ${evidence.wakeAcceptance}\n- model execution: ${evidence.modelExecution}`;
|
|
344
416
|
}
|
|
345
417
|
|
|
346
418
|
function toolResult(details: unknown, markdown: string): AgentToolResult<unknown> {
|
|
@@ -393,7 +465,12 @@ function blockedDeliveryWarning(error: TaskOutboxDeliveryError, target: TaskEndp
|
|
|
393
465
|
return { code: error.code, message: error.message, retryable: false, delivery: "blocked", target, details };
|
|
394
466
|
}
|
|
395
467
|
|
|
396
|
-
function outboxFailureStatus(error: unknown):
|
|
468
|
+
function outboxFailureStatus(error: unknown): string {
|
|
469
|
+
if (error instanceof TaskProtocolError) {
|
|
470
|
+
if (["RELAY_RESET", "RELAY_REBIND_REQUIRED"].includes(error.code)) return "tasks: relay reset; /task-relay-rebind";
|
|
471
|
+
if (error.code === "RELAY_PROFILE_REQUIRED") return "tasks: compatible memory-owned relay required";
|
|
472
|
+
if (error.code === "RELAY_AUTH_REQUIRED") return "tasks: Wolfpack authentication required";
|
|
473
|
+
}
|
|
397
474
|
return error instanceof TaskOutboxDeliveryError && error.code === TARGET_NOT_REGISTERED_CODE ? "tasks: outbox degraded" : "tasks: relay unavailable";
|
|
398
475
|
}
|
|
399
476
|
|
|
@@ -31,7 +31,7 @@ export class InMemoryTaskRelay implements TaskRelay {
|
|
|
31
31
|
private readonly endpoints = new Map<string, TaskEndpoint>();
|
|
32
32
|
private readonly mailboxes = new Map<string, MailboxItem[]>();
|
|
33
33
|
private readonly accepted = new Set<string>();
|
|
34
|
-
private readonly acknowledged = new
|
|
34
|
+
private readonly acknowledged = new Set<string>();
|
|
35
35
|
private nextDelivery = 0;
|
|
36
36
|
private sendFailure = false;
|
|
37
37
|
|
|
@@ -72,11 +72,10 @@ export class InMemoryTaskRelay implements TaskRelay {
|
|
|
72
72
|
this.assertEndpoint(input.endpoint);
|
|
73
73
|
const mailbox = this.mailboxes.get(input.endpoint.id);
|
|
74
74
|
if (!mailbox) throw new TaskProtocolError("UNREGISTERED_TARGET", "endpoint is not registered for this protocol");
|
|
75
|
-
const acknowledged = this.acknowledged.get(input.endpoint.id) ?? 0;
|
|
76
75
|
const cursor = Number(input.cursor);
|
|
77
76
|
if (!Number.isSafeInteger(cursor) || cursor < 0) throw new TaskProtocolError("INVALID_CURSOR", "relay cursor must be a non-negative integer");
|
|
78
|
-
const start =
|
|
79
|
-
const pending = mailbox.filter((item) => Number(item.cursor) > start);
|
|
77
|
+
const start = cursor;
|
|
78
|
+
const pending = mailbox.filter((item) => Number(item.cursor) > start && !this.acknowledged.has(item.envelope.envelopeId));
|
|
80
79
|
const deliveries = pending.slice(0, input.limit);
|
|
81
80
|
return {
|
|
82
81
|
deliveries,
|
|
@@ -89,7 +88,9 @@ export class InMemoryTaskRelay implements TaskRelay {
|
|
|
89
88
|
this.assertEndpoint(input.endpoint);
|
|
90
89
|
const cursor = Number(input.cursor);
|
|
91
90
|
if (!Number.isSafeInteger(cursor) || cursor < 0) throw new TaskProtocolError("INVALID_CURSOR", "relay cursor must be a non-negative integer");
|
|
92
|
-
|
|
91
|
+
const item = this.mailboxes.get(input.endpoint.id)?.find(item => item.cursor === input.cursor);
|
|
92
|
+
if (!item) throw new TaskProtocolError("INVALID_CURSOR", "relay delivery is not in this endpoint mailbox");
|
|
93
|
+
this.acknowledged.add(item.envelope.envelopeId);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
failNextSend(): void {
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export { createInMemoryTaskRelay, InMemoryTaskRelay } from "./in-memory-task-relay";
|
|
2
2
|
export { createTaskCore } from "./task-core";
|
|
3
|
+
export { createConfiguredTaskCore } from "./configured-task-core";
|
|
4
|
+
export type { ConfiguredTaskCoreOptions, OwnedTaskCore } from "./configured-task-core";
|
|
3
5
|
export { createTaskStore } from "./task-store";
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
WOLFPACK_TASK_RELAY_ID,
|
|
8
|
-
WOLFPACK_TASK_RELAY_LEASE_MS,
|
|
9
|
-
WOLFPACK_TASK_RELAY_PROTOCOL_VERSION,
|
|
10
|
-
} from "./wolfpack-task-relay";
|
|
6
|
+
export { createVolatileTaskSession, VOLATILE_PROFILE } from "./volatile-task-session";
|
|
7
|
+
export type { VolatileTaskSession, VolatileTaskSessionOptions } from "./volatile-task-session";
|
|
8
|
+
export { WOLFPACK_TASK_RELAY_ID } from "./wolfpack-endpoint-view";
|
|
11
9
|
export { runTaskRelayConformance } from "./task-relay-conformance";
|
|
12
10
|
export {
|
|
13
11
|
MAX_RELAY_PAYLOAD_BYTES,
|
|
@@ -25,6 +23,7 @@ export type {
|
|
|
25
23
|
RelayInboxPage,
|
|
26
24
|
RelayReceiveRequest,
|
|
27
25
|
RelayTargetReference,
|
|
26
|
+
RelayTransportBinding,
|
|
28
27
|
TaskEndpoint,
|
|
29
28
|
TaskEvent,
|
|
30
29
|
TaskIntent,
|