@loopingai/core 0.6.0 → 0.7.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/dist/a2a/caller-token.d.ts +38 -0
- package/dist/a2a/caller-token.js +61 -0
- package/dist/a2a/deliver.d.ts +49 -0
- package/dist/a2a/deliver.js +49 -0
- package/dist/a2a/index.d.ts +2 -0
- package/dist/a2a/index.js +2 -0
- package/dist/alarm/index.d.ts +77 -0
- package/dist/alarm/index.js +116 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/platform.d.ts +29 -0
- package/dist/platform.js +31 -0
- package/dist/round/workflow.js +23 -78
- package/dist/testing/do.d.ts +4 -4
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +5 -1
- package/package.json +5 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface CallerTokenOptions {
|
|
2
|
+
/** This agent's raw private JWK JSON — the same `A2A_SIGNING_KEY` its card is signed with. */
|
|
3
|
+
signingKey: string;
|
|
4
|
+
/**
|
|
5
|
+
* This agent's own origin. It becomes `iss`, and `jku` is derived from it, and
|
|
6
|
+
* the two must agree: a verifier that accepts a `jku` on a different origin
|
|
7
|
+
* than `iss` lets one allowlisted origin impersonate another.
|
|
8
|
+
*/
|
|
9
|
+
issuer: string;
|
|
10
|
+
/**
|
|
11
|
+
* Who the token is for. Normalized to a bare origin, because a verifier
|
|
12
|
+
* typically derives what it expects from `new URL(request.url).origin` and
|
|
13
|
+
* `jose` compares `aud` byte-for-byte — a trailing slash or a stray path is a
|
|
14
|
+
* 401 on every request with nothing to catch it. Throws on a value that is not
|
|
15
|
+
* an absolute URL, which is the right moment for that to fail.
|
|
16
|
+
*/
|
|
17
|
+
audience: string;
|
|
18
|
+
/** The identity this agent asserts. */
|
|
19
|
+
identity: Record<string, unknown>;
|
|
20
|
+
/** Which tenant of this deployment the token speaks for. */
|
|
21
|
+
tenant: string;
|
|
22
|
+
/** Lifetime in seconds. Defaults to 120. */
|
|
23
|
+
ttlSeconds?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sign a short-lived token identifying **this agent as a caller** to another
|
|
27
|
+
* service that trusts its card key.
|
|
28
|
+
*
|
|
29
|
+
* The production sibling of `makeGatewayToken`, which core previously shipped
|
|
30
|
+
* only from `/testing` — so an agent that had to call out mint-signed had to
|
|
31
|
+
* hand-write this shape, and every deployment that did so wrote its own subtly
|
|
32
|
+
* different version of the `iss`/`jku` agreement above.
|
|
33
|
+
*
|
|
34
|
+
* Distinct from {@link signCallbackJwt}, which carries **no** claims: that one
|
|
35
|
+
* proves "the agent you called is calling you back about this task", where this
|
|
36
|
+
* proves "this is who I am and which tenant I speak for".
|
|
37
|
+
*/
|
|
38
|
+
export declare function signCallerToken(options: CallerTokenOptions): Promise<string>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { importJWK, SignJWT } from "jose";
|
|
2
|
+
import { A2A_JWS_ALG, IDENTITY_CLAIM, TENANT_CLAIM, jwksUrl } from "@loopingai/a2a-protocol";
|
|
3
|
+
import { parsePrivateJwk } from "./card.js";
|
|
4
|
+
/** Default lifetime: long enough for clock skew, short enough to be worthless from a log. */
|
|
5
|
+
const DEFAULT_TTL_SECONDS = 120;
|
|
6
|
+
/**
|
|
7
|
+
* `importJWK` does real work and this is on the per-request path.
|
|
8
|
+
*
|
|
9
|
+
* Keyed by the raw secret so a rotated key invalidates the entry rather than
|
|
10
|
+
* being ignored for the life of the isolate.
|
|
11
|
+
*/
|
|
12
|
+
let cached;
|
|
13
|
+
async function signingKeyFor(raw) {
|
|
14
|
+
if (cached?.raw === raw)
|
|
15
|
+
return cached;
|
|
16
|
+
const jwk = parsePrivateJwk(raw);
|
|
17
|
+
// Not cast to `CryptoKey`: `importJWK` returns a union, and asserting the
|
|
18
|
+
// branch would be a lie the day this key is anything but Ed25519.
|
|
19
|
+
const key = await importJWK(jwk, A2A_JWS_ALG);
|
|
20
|
+
cached = { raw, key, kid: jwk.kid };
|
|
21
|
+
return cached;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Sign a short-lived token identifying **this agent as a caller** to another
|
|
25
|
+
* service that trusts its card key.
|
|
26
|
+
*
|
|
27
|
+
* The production sibling of `makeGatewayToken`, which core previously shipped
|
|
28
|
+
* only from `/testing` — so an agent that had to call out mint-signed had to
|
|
29
|
+
* hand-write this shape, and every deployment that did so wrote its own subtly
|
|
30
|
+
* different version of the `iss`/`jku` agreement above.
|
|
31
|
+
*
|
|
32
|
+
* Distinct from {@link signCallbackJwt}, which carries **no** claims: that one
|
|
33
|
+
* proves "the agent you called is calling you back about this task", where this
|
|
34
|
+
* proves "this is who I am and which tenant I speak for".
|
|
35
|
+
*/
|
|
36
|
+
export async function signCallerToken(options) {
|
|
37
|
+
const { key, kid } = await signingKeyFor(options.signingKey);
|
|
38
|
+
// Normalized for the same reason `audience` is, and it has to happen before
|
|
39
|
+
// both uses: `jku` names only the origin, so an `issuer` carrying a trailing
|
|
40
|
+
// slash or a path would sign an `iss` that disagrees with it — and a verifier
|
|
41
|
+
// comparing `iss` byte-for-byte against a normalized origin allowlist rejects
|
|
42
|
+
// the token even though the two URLs share an origin.
|
|
43
|
+
const issuer = new URL(options.issuer).origin;
|
|
44
|
+
return new SignJWT({
|
|
45
|
+
[IDENTITY_CLAIM]: options.identity,
|
|
46
|
+
[TENANT_CLAIM]: options.tenant
|
|
47
|
+
})
|
|
48
|
+
.setProtectedHeader({
|
|
49
|
+
alg: A2A_JWS_ALG,
|
|
50
|
+
kid,
|
|
51
|
+
// Where the far side fetches the public half. A verifier must check this
|
|
52
|
+
// origin against its own allowlist *before* fetching, which is what stops
|
|
53
|
+
// a forged token nominating an attacker-controlled JWKS.
|
|
54
|
+
jku: jwksUrl(issuer)
|
|
55
|
+
})
|
|
56
|
+
.setIssuer(issuer)
|
|
57
|
+
.setAudience(new URL(options.audience).origin)
|
|
58
|
+
.setIssuedAt()
|
|
59
|
+
.setExpirationTime(`${options.ttlSeconds ?? DEFAULT_TTL_SECONDS}s`)
|
|
60
|
+
.sign(key);
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { WorkflowStep } from "cloudflare:workers";
|
|
2
|
+
import { type TurnPushContext } from "./push.js";
|
|
3
|
+
import type { PlainTask } from "./task.js";
|
|
4
|
+
/** What {@link deliverTerminalTask} needs to finish a turn. */
|
|
5
|
+
export interface DeliverTerminalOptions {
|
|
6
|
+
/** Which Task this is about, and where its callback goes. */
|
|
7
|
+
push: TurnPushContext;
|
|
8
|
+
/** The agent's card-signing key, for the callback JWT. */
|
|
9
|
+
signingKey: string;
|
|
10
|
+
/**
|
|
11
|
+
* The guarded write, and **the cancellation check**. Return whether it
|
|
12
|
+
* applied: `false` must mean the row was already terminal (canceled), which is
|
|
13
|
+
* what suppresses the callback.
|
|
14
|
+
*
|
|
15
|
+
* Resolve any Durable Object stub *inside* this function, never above it — a
|
|
16
|
+
* stub hoisted out of a step body is a live connection that a replay cannot
|
|
17
|
+
* reconnect.
|
|
18
|
+
*/
|
|
19
|
+
saveTask: (task: PlainTask) => Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Build the terminal Task. Called **inside** the `complete` step so that what
|
|
22
|
+
* gets notified is exactly what was persisted: building it outside would
|
|
23
|
+
* re-stamp `new Date()` on every replay and post a Task that differs from the
|
|
24
|
+
* stored one.
|
|
25
|
+
*/
|
|
26
|
+
terminal: () => PlainTask;
|
|
27
|
+
/**
|
|
28
|
+
* Optional best-effort cleanup, run after the write and before the callback.
|
|
29
|
+
* An agent with no managed children omits it and no `sweep` step appears.
|
|
30
|
+
*/
|
|
31
|
+
sweep?: () => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Persist a turn's terminal Task, then notify the gateway.
|
|
35
|
+
*
|
|
36
|
+
* **The guarded write is the cancellation check.** `saveTask` refuses to write a
|
|
37
|
+
* terminal state over a `canceled` row and says so, doing that read and write in
|
|
38
|
+
* one synchronous pass inside the Durable Object. Probing first and saving
|
|
39
|
+
* second would leave a window — between the two calls, and again between this
|
|
40
|
+
* step and `notify` — in which a `tasks/cancel` lands and the gateway still
|
|
41
|
+
* receives a `completed` callback. Keying the notify on "did the write apply"
|
|
42
|
+
* closes it, and that has already been got wrong once.
|
|
43
|
+
*
|
|
44
|
+
* In `/a2a` rather than `/round` because the shape is not a round's: an agent
|
|
45
|
+
* whose turn is a single inference ends it the same way, and importing this from
|
|
46
|
+
* `/round` would put the whole delegation engine in a bundle that must not carry
|
|
47
|
+
* it. Everything it calls already lives here.
|
|
48
|
+
*/
|
|
49
|
+
export declare function deliverTerminalTask(step: WorkflowStep, options: DeliverTerminalOptions): Promise<void>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createPushChannel } from "./push.js";
|
|
2
|
+
/**
|
|
3
|
+
* Persist a turn's terminal Task, then notify the gateway.
|
|
4
|
+
*
|
|
5
|
+
* **The guarded write is the cancellation check.** `saveTask` refuses to write a
|
|
6
|
+
* terminal state over a `canceled` row and says so, doing that read and write in
|
|
7
|
+
* one synchronous pass inside the Durable Object. Probing first and saving
|
|
8
|
+
* second would leave a window — between the two calls, and again between this
|
|
9
|
+
* step and `notify` — in which a `tasks/cancel` lands and the gateway still
|
|
10
|
+
* receives a `completed` callback. Keying the notify on "did the write apply"
|
|
11
|
+
* closes it, and that has already been got wrong once.
|
|
12
|
+
*
|
|
13
|
+
* In `/a2a` rather than `/round` because the shape is not a round's: an agent
|
|
14
|
+
* whose turn is a single inference ends it the same way, and importing this from
|
|
15
|
+
* `/round` would put the whole delegation engine in a bundle that must not carry
|
|
16
|
+
* it. Everything it calls already lives here.
|
|
17
|
+
*/
|
|
18
|
+
export async function deliverTerminalTask(step, options) {
|
|
19
|
+
const task = await step.do("complete", async () => {
|
|
20
|
+
const terminal = options.terminal();
|
|
21
|
+
return (await options.saveTask(terminal)) ? terminal : null;
|
|
22
|
+
});
|
|
23
|
+
if (!task)
|
|
24
|
+
return;
|
|
25
|
+
// Caught, not left to propagate: the terminal Task is already durably saved,
|
|
26
|
+
// so a sweep that still fails once the step's own retries are exhausted must
|
|
27
|
+
// not block the callback — the gateway is owed its result either way.
|
|
28
|
+
if (options.sweep) {
|
|
29
|
+
const sweep = options.sweep;
|
|
30
|
+
try {
|
|
31
|
+
await step.do("sweep", async () => {
|
|
32
|
+
await sweep();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error("[deliver] sweep failed after retries", {
|
|
37
|
+
taskId: options.push.taskId,
|
|
38
|
+
err: String(err)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// A card-key-signed callback POST. Retried by the step on a non-2xx; the
|
|
43
|
+
// terminal messageId is deterministic and the gateway is idempotent and
|
|
44
|
+
// single-use, so retries are safe. If it ultimately fails, the gateway's own
|
|
45
|
+
// reaction backstop clears the pending marker.
|
|
46
|
+
await step.do("notify", async () => {
|
|
47
|
+
await createPushChannel(options.signingKey, options.push).deliver(task);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dist/a2a/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-p
|
|
|
22
22
|
export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken, type GatewayIdentity, type VerifyOptions } from "./verify.js";
|
|
23
23
|
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks, type AgentManifest, type BuildCardOptions, type CardSigningConfig, type WireAgentCard } from "./card.js";
|
|
24
24
|
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
25
|
+
export { deliverTerminalTask, type DeliverTerminalOptions } from "./deliver.js";
|
|
26
|
+
export { signCallerToken, type CallerTokenOptions } from "./caller-token.js";
|
|
25
27
|
export { callerContext } from "./caller.js";
|
|
26
28
|
export { createPushChannel, type PushChannel, type TurnPushContext } from "./push.js";
|
|
27
29
|
export { taskStateLabel, type PlainArtifact, type PlainMessage, type PlainPart, type PlainStatus, type PlainTask } from "./task.js";
|
package/dist/a2a/index.js
CHANGED
|
@@ -22,6 +22,8 @@ export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-p
|
|
|
22
22
|
export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken } from "./verify.js";
|
|
23
23
|
export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks } from "./card.js";
|
|
24
24
|
export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
|
|
25
|
+
export { deliverTerminalTask } from "./deliver.js";
|
|
26
|
+
export { signCallerToken } from "./caller-token.js";
|
|
25
27
|
export { callerContext } from "./caller.js";
|
|
26
28
|
export { createPushChannel } from "./push.js";
|
|
27
29
|
export { taskStateLabel } from "./task.js";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/alarm` — many deadlines over a Durable Object's one alarm.
|
|
3
|
+
*
|
|
4
|
+
* A Durable Object has exactly **one** alarm, and an object that needs to wake
|
|
5
|
+
* for more than one reason cannot simply call `setAlarm` from each of them: the
|
|
6
|
+
* last writer silently wins, and whatever the loser was waiting on never
|
|
7
|
+
* happens. {@link WakeMap} is the fix — one storage row holding every pending
|
|
8
|
+
* intent, and the only thing in a DO that calls `setAlarm`.
|
|
9
|
+
*
|
|
10
|
+
* **Its own subpath, deliberately.** This is useful to a plain `DurableObject`,
|
|
11
|
+
* not only to a {@link LoopingAgent}, so it must be importable without pulling
|
|
12
|
+
* the agent machinery into a bundle.
|
|
13
|
+
*
|
|
14
|
+
* **Why not `Agent.schedule()`.** The `agents` SDK has the same mechanism, but
|
|
15
|
+
* sells it only as a method on `Agent`: adopting it means the object becomes an
|
|
16
|
+
* `Agent`, whose constructor creates `cf_agents_state`, `cf_agents_mcp_servers`
|
|
17
|
+
* and `cf_agents_queues` in that object's SQLite, builds an `MCPClientManager`,
|
|
18
|
+
* and prototype-patches every public method for tracing. For an object whose
|
|
19
|
+
* SQLite is something else already — a container's filesystem, say — that is a
|
|
20
|
+
* large import for a small one. `agents/schedule` is not an alternative: it is a
|
|
21
|
+
* prompt and a zod schema for parsing natural-language dates, not alarm
|
|
22
|
+
* machinery.
|
|
23
|
+
*
|
|
24
|
+
* This owns *when* an object wakes. What it owes on waking is the object's own:
|
|
25
|
+
* `alarm()` reads {@link WakeMap.due} and dispatches.
|
|
26
|
+
*/
|
|
27
|
+
/** One scheduled wake-up. */
|
|
28
|
+
export interface WakeIntent {
|
|
29
|
+
/** Why we are waking. Namespace it, e.g. `sync-retry:container-shell`. */
|
|
30
|
+
key: string;
|
|
31
|
+
/** Epoch ms at which this intent becomes due. */
|
|
32
|
+
notBefore: number;
|
|
33
|
+
/** Retry counter, for the intents that carry one. */
|
|
34
|
+
attempt?: number;
|
|
35
|
+
}
|
|
36
|
+
/** The single storage row holding every intent. Small, and written atomically. */
|
|
37
|
+
export declare const WAKE_KEY = "wake";
|
|
38
|
+
/** How far out {@link WakeMap.repair} re-arms when the handler itself failed. */
|
|
39
|
+
export declare const WAKE_REPAIR_MS = 60000;
|
|
40
|
+
export declare class WakeMap {
|
|
41
|
+
#private;
|
|
42
|
+
constructor(storage: DurableObjectStorage);
|
|
43
|
+
/**
|
|
44
|
+
* Every pending intent, as a **null-prototype** dictionary rebuilt from own
|
|
45
|
+
* entries only.
|
|
46
|
+
*
|
|
47
|
+
* {@link WakeIntent.key} is a caller-supplied string, so an ordinary object
|
|
48
|
+
* literal would let three of them misbehave: `get("toString")` would return an
|
|
49
|
+
* inherited function rather than `undefined`, `clear("constructor")` would
|
|
50
|
+
* treat a key it never held as present, and `set` on `"__proto__"` would hit
|
|
51
|
+
* `Object.prototype`'s setter and change the prototype instead of storing the
|
|
52
|
+
* intent. With no prototype there is nothing to inherit and nothing to poison,
|
|
53
|
+
* and every string round-trips as an ordinary key.
|
|
54
|
+
*/
|
|
55
|
+
all(): Promise<Record<string, WakeIntent>>;
|
|
56
|
+
get(key: string): Promise<WakeIntent | undefined>;
|
|
57
|
+
set(intent: WakeIntent): Promise<void>;
|
|
58
|
+
clear(key: string): Promise<void>;
|
|
59
|
+
/** Every intent whose time has come, earliest first. */
|
|
60
|
+
due(now: number): Promise<WakeIntent[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Point the alarm at the earliest deadline.
|
|
63
|
+
*
|
|
64
|
+
* Only ever moved **earlier**, never later: an alarm that fires too soon finds
|
|
65
|
+
* nothing due, re-arms, and costs one wake-up, whereas an alarm pushed later
|
|
66
|
+
* by a coincidental write silently delays whatever was already waiting. When
|
|
67
|
+
* no intents remain the alarm is deleted outright, so an idle object does not
|
|
68
|
+
* wake on a schedule it has no use for.
|
|
69
|
+
*/
|
|
70
|
+
rearm(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Re-arm shortly, for when the handler failed before it could work out what
|
|
73
|
+
* it owed. Distinct from {@link rearm} because that one trusts the map, and
|
|
74
|
+
* the map is what we just failed to read.
|
|
75
|
+
*/
|
|
76
|
+
repair(now: number): Promise<void>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@loopingai/core/alarm` — many deadlines over a Durable Object's one alarm.
|
|
3
|
+
*
|
|
4
|
+
* A Durable Object has exactly **one** alarm, and an object that needs to wake
|
|
5
|
+
* for more than one reason cannot simply call `setAlarm` from each of them: the
|
|
6
|
+
* last writer silently wins, and whatever the loser was waiting on never
|
|
7
|
+
* happens. {@link WakeMap} is the fix — one storage row holding every pending
|
|
8
|
+
* intent, and the only thing in a DO that calls `setAlarm`.
|
|
9
|
+
*
|
|
10
|
+
* **Its own subpath, deliberately.** This is useful to a plain `DurableObject`,
|
|
11
|
+
* not only to a {@link LoopingAgent}, so it must be importable without pulling
|
|
12
|
+
* the agent machinery into a bundle.
|
|
13
|
+
*
|
|
14
|
+
* **Why not `Agent.schedule()`.** The `agents` SDK has the same mechanism, but
|
|
15
|
+
* sells it only as a method on `Agent`: adopting it means the object becomes an
|
|
16
|
+
* `Agent`, whose constructor creates `cf_agents_state`, `cf_agents_mcp_servers`
|
|
17
|
+
* and `cf_agents_queues` in that object's SQLite, builds an `MCPClientManager`,
|
|
18
|
+
* and prototype-patches every public method for tracing. For an object whose
|
|
19
|
+
* SQLite is something else already — a container's filesystem, say — that is a
|
|
20
|
+
* large import for a small one. `agents/schedule` is not an alternative: it is a
|
|
21
|
+
* prompt and a zod schema for parsing natural-language dates, not alarm
|
|
22
|
+
* machinery.
|
|
23
|
+
*
|
|
24
|
+
* This owns *when* an object wakes. What it owes on waking is the object's own:
|
|
25
|
+
* `alarm()` reads {@link WakeMap.due} and dispatches.
|
|
26
|
+
*/
|
|
27
|
+
/** The single storage row holding every intent. Small, and written atomically. */
|
|
28
|
+
export const WAKE_KEY = "wake";
|
|
29
|
+
/** How far out {@link WakeMap.repair} re-arms when the handler itself failed. */
|
|
30
|
+
export const WAKE_REPAIR_MS = 60_000;
|
|
31
|
+
export class WakeMap {
|
|
32
|
+
#storage;
|
|
33
|
+
constructor(storage) {
|
|
34
|
+
this.#storage = storage;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Every pending intent, as a **null-prototype** dictionary rebuilt from own
|
|
38
|
+
* entries only.
|
|
39
|
+
*
|
|
40
|
+
* {@link WakeIntent.key} is a caller-supplied string, so an ordinary object
|
|
41
|
+
* literal would let three of them misbehave: `get("toString")` would return an
|
|
42
|
+
* inherited function rather than `undefined`, `clear("constructor")` would
|
|
43
|
+
* treat a key it never held as present, and `set` on `"__proto__"` would hit
|
|
44
|
+
* `Object.prototype`'s setter and change the prototype instead of storing the
|
|
45
|
+
* intent. With no prototype there is nothing to inherit and nothing to poison,
|
|
46
|
+
* and every string round-trips as an ordinary key.
|
|
47
|
+
*/
|
|
48
|
+
async all() {
|
|
49
|
+
const stored = await this.#storage.get(WAKE_KEY);
|
|
50
|
+
const intents = Object.create(null);
|
|
51
|
+
// `Object.entries` is own-enumerable-only, so nothing from a prototype can
|
|
52
|
+
// enter here even if the stored value arrived with one.
|
|
53
|
+
if (stored) {
|
|
54
|
+
for (const [key, intent] of Object.entries(stored))
|
|
55
|
+
intents[key] = intent;
|
|
56
|
+
}
|
|
57
|
+
return intents;
|
|
58
|
+
}
|
|
59
|
+
async get(key) {
|
|
60
|
+
return (await this.all())[key];
|
|
61
|
+
}
|
|
62
|
+
async set(intent) {
|
|
63
|
+
const all = await this.all();
|
|
64
|
+
all[intent.key] = intent;
|
|
65
|
+
await this.#storage.put(WAKE_KEY, all);
|
|
66
|
+
await this.rearm();
|
|
67
|
+
}
|
|
68
|
+
async clear(key) {
|
|
69
|
+
const all = await this.all();
|
|
70
|
+
// `hasOwn`, not `in`: the dictionary has no prototype today, and this stays
|
|
71
|
+
// correct if that ever changes.
|
|
72
|
+
if (!Object.hasOwn(all, key))
|
|
73
|
+
return;
|
|
74
|
+
delete all[key];
|
|
75
|
+
await this.#storage.put(WAKE_KEY, all);
|
|
76
|
+
await this.rearm();
|
|
77
|
+
}
|
|
78
|
+
/** Every intent whose time has come, earliest first. */
|
|
79
|
+
async due(now) {
|
|
80
|
+
return Object.values(await this.all())
|
|
81
|
+
.filter((intent) => intent.notBefore <= now)
|
|
82
|
+
.sort((a, b) => a.notBefore - b.notBefore);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Point the alarm at the earliest deadline.
|
|
86
|
+
*
|
|
87
|
+
* Only ever moved **earlier**, never later: an alarm that fires too soon finds
|
|
88
|
+
* nothing due, re-arms, and costs one wake-up, whereas an alarm pushed later
|
|
89
|
+
* by a coincidental write silently delays whatever was already waiting. When
|
|
90
|
+
* no intents remain the alarm is deleted outright, so an idle object does not
|
|
91
|
+
* wake on a schedule it has no use for.
|
|
92
|
+
*/
|
|
93
|
+
async rearm() {
|
|
94
|
+
const deadlines = Object.values(await this.all()).map((i) => i.notBefore);
|
|
95
|
+
const existing = await this.#storage.getAlarm();
|
|
96
|
+
if (deadlines.length === 0) {
|
|
97
|
+
if (existing !== null)
|
|
98
|
+
await this.#storage.deleteAlarm();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const earliest = Math.min(...deadlines);
|
|
102
|
+
if (existing === null || existing > earliest) {
|
|
103
|
+
await this.#storage.setAlarm(earliest);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Re-arm shortly, for when the handler failed before it could work out what
|
|
108
|
+
* it owed. Distinct from {@link rearm} because that one trusts the map, and
|
|
109
|
+
* the map is what we just failed to read.
|
|
110
|
+
*/
|
|
111
|
+
async repair(now) {
|
|
112
|
+
const existing = await this.#storage.getAlarm();
|
|
113
|
+
if (existing === null)
|
|
114
|
+
await this.#storage.setAlarm(now + WAKE_REPAIR_MS);
|
|
115
|
+
}
|
|
116
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,6 @@ export type { DelegationNames, RecipeLimits, ResolvedRecipe, SubtaskParams, Subt
|
|
|
14
14
|
export { RecipeValidationError, resolveLimits, validateRecipe, type RecipePolicy } from "./contract/validation.js";
|
|
15
15
|
export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig, type AgentLimits, type CoreConfig, type CoreConfigOverrides, type ModelConfig, type SessionConfig } from "./config.js";
|
|
16
16
|
export { parseGatewayOrigins, type A2ASecretsEnv, type AiEnv, type CoreEnv } from "./env.js";
|
|
17
|
-
export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
17
|
+
export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
18
18
|
export type { PluginStore } from "./db/db.js";
|
|
19
19
|
export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES, type WorkspaceBacking, type WorkspaceEntry, type WorkspaceHandle } from "./subagent/workspace.js";
|
package/dist/index.js
CHANGED
|
@@ -13,5 +13,5 @@ export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools } from ".
|
|
|
13
13
|
export { RecipeValidationError, resolveLimits, validateRecipe } from "./contract/validation.js";
|
|
14
14
|
export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig } from "./config.js";
|
|
15
15
|
export { parseGatewayOrigins } from "./env.js";
|
|
16
|
-
export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
16
|
+
export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
|
|
17
17
|
export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES } from "./subagent/workspace.js";
|
package/dist/platform.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkflowStepConfig } from "cloudflare:workers";
|
|
1
2
|
/**
|
|
2
3
|
* What the Cloudflare Workflows runtime imposes, and the two numbers derived from
|
|
3
4
|
* it. Nothing here is a budget or a preference — see {@link file://./config.ts}
|
|
@@ -107,3 +108,31 @@ export declare const MAX_TOOL_CALL_MS: number;
|
|
|
107
108
|
* 2. The worst-case step product stays under {@link STEPS_PER_INSTANCE}.
|
|
108
109
|
*/
|
|
109
110
|
export declare const MAX_CHUNKS_PER_BRANCH = 40;
|
|
111
|
+
/**
|
|
112
|
+
* What a step holding a model call or a container command configures instead of
|
|
113
|
+
* inheriting Workflows' defaults. Both defaults were measured wrong for this
|
|
114
|
+
* workload.
|
|
115
|
+
*
|
|
116
|
+
* **`timeout`.** The default is ten minutes. A step here holds a model call and
|
|
117
|
+
* its provider retries, or a container command running a project's test suite;
|
|
118
|
+
* neither fits in ten minutes reliably, and neither uses meaningful CPU while it
|
|
119
|
+
* waits. Left inherited, that default silently became the ceiling
|
|
120
|
+
* {@link CHUNK_SOFT_MS} was sized against.
|
|
121
|
+
*
|
|
122
|
+
* **`retries`.** The default is five attempts with exponential backoff from ten
|
|
123
|
+
* seconds. Against a fault that is not transient — a severed Durable Object stub
|
|
124
|
+
* — that produced five failures in under 10ms each, spread across 160 seconds of
|
|
125
|
+
* backoff that bought nothing. Three attempts still cover a genuinely transient
|
|
126
|
+
* fault, since the model call has its own provider-level retry underneath this,
|
|
127
|
+
* and a flat five-second delay stops a fast permanent failure being paid for at
|
|
128
|
+
* exponential rates.
|
|
129
|
+
*
|
|
130
|
+
* Here rather than in `/round` because the agent that most needs it may not be a
|
|
131
|
+
* round agent: a single-inference agent runs one model call in one step and has
|
|
132
|
+
* the same two problems, and importing this from `/round` would put the whole
|
|
133
|
+
* delegation engine in its bundle.
|
|
134
|
+
*
|
|
135
|
+
* For the **short** bookkeeping steps — `working`, `complete`, `notify` and
|
|
136
|
+
* friends — the defaults are fine and a shared config would only hide that.
|
|
137
|
+
*/
|
|
138
|
+
export declare const CHUNK_STEP: WorkflowStepConfig;
|
package/dist/platform.js
CHANGED
|
@@ -107,3 +107,34 @@ export const MAX_TOOL_CALL_MS = 10 * 60_000;
|
|
|
107
107
|
* 2. The worst-case step product stays under {@link STEPS_PER_INSTANCE}.
|
|
108
108
|
*/
|
|
109
109
|
export const MAX_CHUNKS_PER_BRANCH = 40;
|
|
110
|
+
/**
|
|
111
|
+
* What a step holding a model call or a container command configures instead of
|
|
112
|
+
* inheriting Workflows' defaults. Both defaults were measured wrong for this
|
|
113
|
+
* workload.
|
|
114
|
+
*
|
|
115
|
+
* **`timeout`.** The default is ten minutes. A step here holds a model call and
|
|
116
|
+
* its provider retries, or a container command running a project's test suite;
|
|
117
|
+
* neither fits in ten minutes reliably, and neither uses meaningful CPU while it
|
|
118
|
+
* waits. Left inherited, that default silently became the ceiling
|
|
119
|
+
* {@link CHUNK_SOFT_MS} was sized against.
|
|
120
|
+
*
|
|
121
|
+
* **`retries`.** The default is five attempts with exponential backoff from ten
|
|
122
|
+
* seconds. Against a fault that is not transient — a severed Durable Object stub
|
|
123
|
+
* — that produced five failures in under 10ms each, spread across 160 seconds of
|
|
124
|
+
* backoff that bought nothing. Three attempts still cover a genuinely transient
|
|
125
|
+
* fault, since the model call has its own provider-level retry underneath this,
|
|
126
|
+
* and a flat five-second delay stops a fast permanent failure being paid for at
|
|
127
|
+
* exponential rates.
|
|
128
|
+
*
|
|
129
|
+
* Here rather than in `/round` because the agent that most needs it may not be a
|
|
130
|
+
* round agent: a single-inference agent runs one model call in one step and has
|
|
131
|
+
* the same two problems, and importing this from `/round` would put the whole
|
|
132
|
+
* delegation engine in its bundle.
|
|
133
|
+
*
|
|
134
|
+
* For the **short** bookkeeping steps — `working`, `complete`, `notify` and
|
|
135
|
+
* friends — the defaults are fine and a shared config would only hide that.
|
|
136
|
+
*/
|
|
137
|
+
export const CHUNK_STEP = {
|
|
138
|
+
timeout: STEP_TIMEOUT_MS,
|
|
139
|
+
retries: { limit: 3, delay: 5_000, backoff: "constant" }
|
|
140
|
+
};
|
package/dist/round/workflow.js
CHANGED
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
import { MAX_CHUNKS_PER_BRANCH, STEP_TIMEOUT_MS } from "../platform.js";
|
|
1
|
+
import { CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, STEP_TIMEOUT_MS } from "../platform.js";
|
|
2
2
|
import { buildCompletedTask, buildFailedTask } from "../a2a/notify.js";
|
|
3
|
-
import {
|
|
4
|
-
/**
|
|
5
|
-
* What the long steps configure instead of inheriting.
|
|
6
|
-
*
|
|
7
|
-
* Both halves of this were previously left at Workflows' defaults, and both
|
|
8
|
-
* defaults were wrong for this workload.
|
|
9
|
-
*
|
|
10
|
-
* **`timeout`.** The default is ten minutes. Nothing here passed a config, so that
|
|
11
|
-
* default silently became the ceiling {@link CHUNK_SOFT_MS} was sized against —
|
|
12
|
-
* see the note on `STEP_TIMEOUT_MS` in `platform.ts` for what that cost. A step
|
|
13
|
-
* here holds a model call and its provider retries, or a container command running
|
|
14
|
-
* a project's test suite; neither fits in ten minutes reliably and neither uses
|
|
15
|
-
* meaningful CPU while it waits.
|
|
16
|
-
*
|
|
17
|
-
* **`retries`.** The default is five attempts with exponential backoff from ten
|
|
18
|
-
* seconds. The failure documented on {@link ResolveAgent} is what that produces
|
|
19
|
-
* when the fault is not transient: five retries against a severed stub, each
|
|
20
|
-
* failing in under 10ms, spread across 160 seconds of backoff that bought nothing.
|
|
21
|
-
* Three attempts still cover a genuinely transient fault — the model call has its
|
|
22
|
-
* own provider-level retry underneath this — and a flat five-second delay stops a
|
|
23
|
-
* fast, permanent failure from being paid for at exponential rates.
|
|
24
|
-
*
|
|
25
|
-
* Deliberately applied only to the chunk steps — see {@link turnStep} for why a
|
|
26
|
-
* round does not share it. The short bookkeeping steps (`working`, `deadline:`,
|
|
27
|
-
* `scan:`, `complete`, `notify`) are sub-second projections where the defaults
|
|
28
|
-
* are fine and a shared config would only hide that.
|
|
29
|
-
*/
|
|
30
|
-
const CHUNK_STEP = {
|
|
31
|
-
timeout: STEP_TIMEOUT_MS,
|
|
32
|
-
retries: { limit: 3, delay: 5_000, backoff: "constant" }
|
|
33
|
-
};
|
|
3
|
+
import { deliverTerminalTask } from "../a2a/deliver.js";
|
|
34
4
|
/**
|
|
35
5
|
* The same retries, and a timeout a **round** can actually be measured against.
|
|
36
6
|
*
|
|
@@ -323,62 +293,37 @@ async function runBranch(p, step, agent, id, push) {
|
|
|
323
293
|
* and is deliberately not given a kind of its own until something needs to tell
|
|
324
294
|
* it apart.
|
|
325
295
|
*
|
|
326
|
-
* The
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* **The guarded write is the cancellation check.** `saveTask` refuses to write a
|
|
331
|
-
* terminal state over a `canceled` row and says so, and it does that read and
|
|
332
|
-
* write in one synchronous pass inside the DO. Probing first and saving second
|
|
333
|
-
* would leave a window — between the two calls, and again between this step and
|
|
334
|
-
* `notify` — in which a `tasks/cancel` lands and the gateway still receives a
|
|
335
|
-
* `completed` callback. Keying the notify on "did the write apply" closes it.
|
|
296
|
+
* The delivery itself is {@link deliverTerminalTask}, which is shared with agents
|
|
297
|
+
* that never delegate. What is a round's own is the two things passed to it: the
|
|
298
|
+
* choice of terminal Task, and the child sweep.
|
|
336
299
|
*/
|
|
337
300
|
async function deliver(p, step, agent, reply, deps, failure) {
|
|
338
301
|
// Resolved outside the step body so a replay cannot take a different branch
|
|
339
302
|
// than the write it is replaying.
|
|
340
303
|
const failedText = (failure && deps.failureCopy?.(failure.kind, failure.detail)) ||
|
|
341
304
|
deps.policy.copy.taskFailed;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
? buildCompletedTask(p.taskId, p.contextId, reply)
|
|
345
|
-
: buildFailedTask(p.taskId, p.contextId, failedText);
|
|
346
|
-
return (await agent().saveTask(terminal)) ? terminal : null;
|
|
347
|
-
});
|
|
348
|
-
if (!task)
|
|
349
|
-
return;
|
|
350
|
-
// Sweep this Task's managed children now that it is terminal and every `execute`
|
|
351
|
-
// step has unwound. Deleting them here — rather than right after each successful
|
|
352
|
-
// chunk — keeps `deleteSubAgent`'s facet-abort from landing on a still-open
|
|
353
|
-
// `executeChunk` RPC, which telemetry mis-records as a failure. Best-effort and
|
|
354
|
-
// idempotent, so it is safe on replay.
|
|
355
|
-
//
|
|
356
|
-
// Caught, not left to propagate: the terminal Task is already durably saved, so
|
|
357
|
-
// a sweep that still fails once the step's own retries are exhausted must not
|
|
358
|
-
// block `notify` below — the gateway is owed its result regardless of whether
|
|
359
|
-
// this Task's children were reclaimed.
|
|
360
|
-
try {
|
|
361
|
-
await step.do("sweep", async () => {
|
|
362
|
-
await agent().sweepTaskChildren(p.taskId);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
catch (err) {
|
|
366
|
-
console.error("[handle-task] sweep failed after retries", {
|
|
367
|
-
taskId: p.taskId,
|
|
368
|
-
err: String(err)
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
// Notify the gateway: a card-key-signed callback POST. Retried by the step on a
|
|
372
|
-
// non-2xx; the terminal messageId is deterministic and the gateway is
|
|
373
|
-
// idempotent/single-use, so retries are safe. If it ultimately fails, the
|
|
374
|
-
// gateway's own reaction backstop clears the pending marker.
|
|
375
|
-
await step.do("notify", async () => {
|
|
376
|
-
await createPushChannel(deps.signingKey, {
|
|
305
|
+
await deliverTerminalTask(step, {
|
|
306
|
+
push: {
|
|
377
307
|
taskId: p.taskId,
|
|
378
308
|
contextId: p.contextId,
|
|
379
309
|
pushUrl: p.pushUrl,
|
|
380
310
|
pushToken: p.pushToken,
|
|
381
311
|
jku: p.jku
|
|
382
|
-
}
|
|
312
|
+
},
|
|
313
|
+
signingKey: deps.signingKey,
|
|
314
|
+
// `agent()` inside the body, never hoisted: a stub is a live connection and
|
|
315
|
+
// a severed one never reconnects.
|
|
316
|
+
saveTask: (task) => agent().saveTask(task),
|
|
317
|
+
terminal: () => reply !== null
|
|
318
|
+
? buildCompletedTask(p.taskId, p.contextId, reply)
|
|
319
|
+
: buildFailedTask(p.taskId, p.contextId, failedText),
|
|
320
|
+
// Sweep this Task's managed children now that it is terminal and every
|
|
321
|
+
// `execute` step has unwound. Deleting them here — rather than right after
|
|
322
|
+
// each successful chunk — keeps `deleteSubAgent`'s facet-abort from landing
|
|
323
|
+
// on a still-open `executeChunk` RPC, which telemetry mis-records as a
|
|
324
|
+
// failure. Best-effort and idempotent, so it is safe on replay.
|
|
325
|
+
sweep: async () => {
|
|
326
|
+
await agent().sweepTaskChildren(p.taskId);
|
|
327
|
+
}
|
|
383
328
|
});
|
|
384
329
|
}
|
package/dist/testing/do.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ import { AgentDB, type AgentDBOptions } from "../db/db.js";
|
|
|
8
8
|
* and {@link DoTestHelpers.withDb} hands over the whole `AgentDB` rather than
|
|
9
9
|
* pre-selecting a table.
|
|
10
10
|
*/
|
|
11
|
-
export interface DoTestHelpers {
|
|
11
|
+
export interface DoTestHelpers<T extends Rpc.DurableObjectBranded | undefined> {
|
|
12
12
|
/** Fresh, unique DO stub per test — state never leaks between tests. */
|
|
13
|
-
freshStub(label: string): DurableObjectStub
|
|
13
|
+
freshStub(label: string): DurableObjectStub<T>;
|
|
14
14
|
/** Run `fn` inside a fresh DO instance with a migrated {@link AgentDB}. */
|
|
15
|
-
withDb<
|
|
15
|
+
withDb<R>(label: string, fn: (db: AgentDB) => R): Promise<R>;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* `ctx` is protected in the DO type system but public at runtime. Cast once so
|
|
@@ -26,4 +26,4 @@ export declare function doStorage(instance: unknown): DurableObjectStorage;
|
|
|
26
26
|
* const { freshStub, withDb } = makeDoHelpers(env.MyAgent);
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
export declare function makeDoHelpers(ns: DurableObjectNamespace
|
|
29
|
+
export declare function makeDoHelpers<T extends Rpc.DurableObjectBranded | undefined = undefined>(ns: DurableObjectNamespace<T>, defaults?: AgentDBOptions): DoTestHelpers<T>;
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
export { setupRecording, cassetteNameFor, type SetupRecordingOptions } from "./vcr-spec.js";
|
|
25
25
|
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE, type VcrReleaseResult } from "./vcr-shared.js";
|
|
26
26
|
export { FakeSession } from "./fake-session.js";
|
|
27
|
-
export { mockModel, finalReply, type MockStep } from "./mock-model.js";
|
|
27
|
+
export { mockModel, finalReply, throwingModel, countingModel, rateLimitedModel, type MockStep } from "./mock-model.js";
|
|
28
28
|
export { makeGatewayToken, type GatewayTokenOptions } from "./auth.js";
|
|
29
29
|
export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, TEST_MODELS, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
|
|
30
30
|
export { doStorage, makeDoHelpers, type DoTestHelpers } from "./do.js";
|
package/dist/testing/index.js
CHANGED
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
export { setupRecording, cassetteNameFor } from "./vcr-spec.js";
|
|
25
25
|
export { VCR_CONTROL_ORIGIN, VCR_MARKER_HEADER, CASSETTE_NAME_RE } from "./vcr-shared.js";
|
|
26
26
|
export { FakeSession } from "./fake-session.js";
|
|
27
|
-
|
|
27
|
+
// `throwingModel`, `countingModel` and `rateLimitedModel` were reachable only
|
|
28
|
+
// through a deep `dist/` path until now, which meant a consumer could not assert
|
|
29
|
+
// the one thing they exist for: that a rate limit is waited out on the *same*
|
|
30
|
+
// model rather than falling through to the fallback slot.
|
|
31
|
+
export { mockModel, finalReply, throwingModel, countingModel, rateLimitedModel } from "./mock-model.js";
|
|
28
32
|
export { makeGatewayToken } from "./auth.js";
|
|
29
33
|
export { AGENT_ORIGIN, GATEWAY_ORIGIN, TEST_AGENT_PRIVATE_JWK, TEST_GATEWAY_PRIVATE_JWK, TEST_MODELS, gatewayPublicJwks, testAgentMessage, testStatus, testTask } from "./fixtures.js";
|
|
30
34
|
export { doStorage, makeDoHelpers } from "./do.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopingai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Shared, mandatory foundation for Looping agents on Cloudflare Workers: zero-trust A2A, durable task lifecycle, delegation and subagent runtime, test harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"types": "./dist/host/index.d.ts",
|
|
58
58
|
"import": "./dist/host/index.js"
|
|
59
59
|
},
|
|
60
|
+
"./alarm": {
|
|
61
|
+
"types": "./dist/alarm/index.d.ts",
|
|
62
|
+
"import": "./dist/alarm/index.js"
|
|
63
|
+
},
|
|
60
64
|
"./round": {
|
|
61
65
|
"types": "./dist/round/index.d.ts",
|
|
62
66
|
"import": "./dist/round/index.js"
|