@desplega.ai/agent-swarm 2.4.3 → 2.4.4
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/{acp-adapter-1885dpr7.js → acp-adapter-thq04adh.js} +1 -2
- package/dist/{actions-pn76n32q.js → actions-j6ywh6x5.js} +17 -17
- package/dist/{amp-adapter-af84rx1d.js → amp-adapter-k8b5xmj8.js} +2 -2
- package/dist/{app-6sv8mhzm.js → app-rb7vc02s.js} +3 -3
- package/dist/{assistant-sc2tf825.js → assistant-ftta1zjf.js} +18 -18
- package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-srp6v6af.js} +1 -1
- package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-xns7h5h9.js} +1 -1
- package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-3qqrh2mz.js} +2 -2
- package/dist/{boot-seeds-37sp626b.js → boot-seeds-9ks8hce7.js} +4 -4
- package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-k5ksyres.js} +2 -2
- package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-n5a5eqks.js} +1 -1
- package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-2j3j4kwn.js} +2 -2
- package/dist/{cli-v87dr6e3.js → cli-1d70crsy.js} +1 -1
- package/dist/{cli-1ma335fz.js → cli-1ephgwnq.js} +2 -2
- package/dist/{cli-4yef8ghv.js → cli-2v045xyf.js} +3 -3
- package/dist/{cli-73ezfhha.js → cli-2y91wnbh.js} +10 -5
- package/dist/{cli-1eszr343.js → cli-2zqpkvtt.js} +1 -1
- package/dist/{cli-e4d78xsv.js → cli-37yb7307.js} +1 -1
- package/dist/{cli-8y99kfey.js → cli-3cgngyd8.js} +9 -9
- package/dist/{cli-wk28hzqn.js → cli-44159k28.js} +1 -1
- package/dist/{cli-qp2m4btv.js → cli-4dg55wsk.js} +2 -2
- package/dist/{cli-0dxxwfkt.js → cli-51y09c6p.js} +17 -14
- package/dist/{cli-aa3tb29r.js → cli-55qmbp6z.js} +1 -1
- package/dist/{cli-mzkwkbdy.js → cli-82d3nkc3.js} +1 -1
- package/dist/{cli-sds5zzs8.js → cli-9fhkkpj5.js} +78 -12
- package/dist/{cli-vq9gqarq.js → cli-amyhm3gj.js} +1 -1
- package/dist/{cli-bxrqvpg5.js → cli-b8k7epj1.js} +5 -5
- package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
- package/dist/{cli-58vwn100.js → cli-bnz0r3kc.js} +1 -1
- package/dist/{cli-6eyncg0y.js → cli-cynvc9fj.js} +34 -6
- package/dist/{cli-f0p0f6ja.js → cli-dqb4n629.js} +7 -7
- package/dist/{cli-pygdsgh8.js → cli-esxerzg7.js} +1 -1
- package/dist/{cli-dm8p2dpf.js → cli-gqvh8d8a.js} +6 -6
- package/dist/{cli-p0qerd2r.js → cli-hj3debgw.js} +1 -1
- package/dist/{cli-836ehxmt.js → cli-nc35pxb7.js} +2 -2
- package/dist/{cli-vcgscaan.js → cli-p20r8w42.js} +8 -6
- package/dist/{cli-1xsraagq.js → cli-pccfmtb5.js} +4 -4
- package/dist/{cli-s9rr4gr2.js → cli-q6r7t299.js} +1036 -298
- package/dist/{cli-tkf4x6ha.js → cli-qkhc5aqy.js} +11 -11
- package/dist/{cli-1n9gdg56.js → cli-t7cyef5g.js} +4 -4
- package/dist/{cli-038fga5k.js → cli-te6b5wvg.js} +148 -183
- package/dist/{cli-kgnkyabk.js → cli-v3yr2gbg.js} +109 -35
- package/dist/{cli-h0mgxann.js → cli-vdm20wej.js} +2 -2
- package/dist/{cli-zvbb4n0e.js → cli-vq97g1jq.js} +2 -2
- package/dist/{cli-gx0j2khs.js → cli-w6nadkh4.js} +3 -3
- package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
- package/dist/{cli-npfe9t1s.js → cli-xc27fwzg.js} +2 -2
- package/dist/{cli-6aen3ezb.js → cli-xj0re5tm.js} +1 -1
- package/dist/{cli-aypbg9r2.js → cli-zrk6jxv8.js} +1 -1
- package/dist/{cli-1012xb7j.js → cli-zrwpctp4.js} +20 -20
- package/dist/cli.js +41 -16
- package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-968xwfy7.js} +1 -1
- package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-ht4z99fa.js} +1 -1
- package/dist/{commands-hd574th6.js → commands-pcymaeg9.js} +2 -2
- package/dist/{controller-pz5mcfg4.js → controller-fpjyk46r.js} +2 -2
- package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-mwzx1fh6.js} +18 -18
- package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-cp4s2e2f.js} +1 -1
- package/dist/{db-h06dxp05.js → db-sgd12bt9.js} +3 -3
- package/dist/{db-6nf1207q.js → db-xsjg9jqq.js} +1 -1
- package/dist/{dispatcher-zthpy4m1.js → dispatcher-x0jfw2c9.js} +16 -16
- package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-jrq7ma7h.js} +1 -1
- package/dist/{external-channel-x01t7ke6.js → external-channel-8njj4h3b.js} +2 -2
- package/dist/{grok-adapter-h21jv594.js → grok-adapter-kkr97v2f.js} +2 -3
- package/dist/handlers-0pvee9nw.js +39 -0
- package/dist/{heartbeat-zzfksckz.js → heartbeat-wd03zhn0.js} +19 -19
- package/dist/{hook-z4zhzj1f.js → hook-f0vghj42.js} +1 -1
- package/dist/{hook-3xfqhh2s.js → hook-xzj2ts2p.js} +1 -1
- package/dist/{http-q813tmya.js → http-4hwxv62p.js} +255 -117
- package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
- package/dist/{index-220tk9bw.js → index-97q4xn03.js} +19 -10
- package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
- package/dist/{index-m0a4ydqj.js → index-gdh8jrdm.js} +16 -16
- package/dist/{index-wbbs14c5.js → index-hg0x2cdv.js} +16 -16
- package/dist/{index-d0g18bax.js → index-thm0y6a8.js} +19 -19
- package/dist/{index-x8xbyn1t.js → index-ww2vznkp.js} +1 -1
- package/dist/{keepalive-y5t1my7m.js → keepalive-hrh7xnr9.js} +3 -3
- package/dist/{lead-2xwtq66b.js → lead-ee8vazz2.js} +26 -27
- package/dist/{maintenance-ybfsaedv.js → maintenance-e8wnwnqx.js} +1 -1
- package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-zm0yj4sj.js} +2 -2
- package/dist/{onboard-xn6fkkys.js → onboard-dtwcq603.js} +2 -2
- package/dist/{otel-impl-e6mn0z13.js → otel-impl-w3fbhpn0.js} +1 -1
- package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-j38dehfs.js} +3 -3
- package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-ae8yttq4.js} +2 -2
- package/dist/{rbac-roles-mb470p4e.js → rbac-roles-ejxx9b35.js} +2 -2
- package/dist/{render-v2-g0306nh1.js → render-v2-n16n0phe.js} +3 -3
- package/dist/{resume-v315hnq6.js → resume-z0fgjc38.js} +5 -5
- package/dist/{session-access-2zg285a1.js → session-access-cv3mq6mc.js} +6 -4
- package/dist/{setup-a4mtf02k.js → setup-mafhj3yt.js} +1 -1
- package/dist/{work-objects-9fr0fgtn.js → work-objects-8b86mgjp.js} +19 -19
- package/dist/{worker-qae22jeb.js → worker-16s75hm5.js} +26 -27
- package/dist/worker-token-xf11yyds.js +136 -0
- package/openapi.json +295 -12
- package/package.json +4 -4
- package/src/apps/action-runner.ts +4 -0
- package/src/apps/catalog.generated.json +3 -0
- package/src/apps/page-validator.ts +8 -2
- package/src/be/migrations/232_agent_session_tokens.sql +41 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +10 -1
- package/src/be/swarm-config-guard.ts +16 -0
- package/src/be/users.ts +169 -12
- package/src/cli.tsx +24 -0
- package/src/commands/worker-token.ts +164 -0
- package/src/http/apps.ts +43 -5
- package/src/http/codex-oauth-keep-warm.ts +27 -9
- package/src/http/config.ts +5 -3
- package/src/http/core.ts +2 -0
- package/src/http/session-access.ts +51 -2
- package/src/http/sessions.ts +249 -10
- package/src/http/tasks.ts +4 -32
- package/src/providers/acp-adapter.ts +33 -13
- package/src/providers/acp-swarm-events.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-oauth/storage.ts +1 -1
- package/src/rbac/legacy-policy.ts +3 -1
- package/src/rbac/permissions.ts +4 -0
- package/src/tests/acp-adapter.test.ts +10 -23
- package/src/tests/acp-fake-agent.ts +36 -0
- package/src/tests/acp-swarm-events.test.ts +1 -1
- package/src/tests/agent-session-tokens.test.ts +452 -0
- package/src/tests/apps-action-parent-task.test.ts +295 -0
- package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
- package/src/tests/config-session-auth.test.ts +14 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/grok-adapter.test.ts +3 -3
- package/src/tests/heartbeat-jev-gate.test.ts +23 -0
- package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/session-route-posture.test.ts +2 -0
- package/src/tests/worker-token-cli.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-system-one-extraction.test.ts +805 -0
- package/src/types.ts +2 -1
- package/src/utils/request-auth-context.ts +2 -1
- package/src/workflows/definition.ts +7 -1
- package/src/workflows/engine.ts +18 -12
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +56 -255
- package/src/workflows/executors/system-one-extract-wire.ts +57 -0
- package/src/workflows/executors/system-one-extraction.ts +830 -0
- package/src/workflows/executors/system-one-providers.ts +277 -110
- package/src/workflows/executors/system-one-transport.ts +264 -0
- package/templates/official/lead/HEARTBEAT.md +1 -1
- package/templates/skills/apps/SKILL.md +3 -1
- package/templates/skills/apps/content.md +3 -1
- package/templates/skills/heartbeat-runbook/content.md +3 -3
- package/dist/cli-v1sjhn52.js +0 -7
- package/dist/handlers-2qjw434w.js +0 -39
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { getOpenRouterAttributionHeaders } from "../../utils/openrouter-base-url";
|
|
2
|
+
import {
|
|
3
|
+
type SystemOneCapability,
|
|
4
|
+
type SystemOneHostId,
|
|
5
|
+
systemOneKeyRejectedMessage,
|
|
6
|
+
} from "./system-one-providers";
|
|
7
|
+
import { cloudflareErrorCode, isRecord } from "./system-one-wire";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One POST to a System One host, shared by every capability (`system-one-decision`,
|
|
11
|
+
* `system-one-extraction`): one time budget, transient retries with backoff,
|
|
12
|
+
* `Retry-After`, redirect refusal, a response size cap, and the same error
|
|
13
|
+
* sentences. The credential goes only into the `authorization` header.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const SYSTEM_ONE_DEFAULT_TIMEOUT_MS = 30_000;
|
|
17
|
+
export const SYSTEM_ONE_MIN_TIMEOUT_MS = 1_000;
|
|
18
|
+
export const SYSTEM_ONE_MAX_TIMEOUT_MS = 300_000;
|
|
19
|
+
export const SYSTEM_ONE_DEFAULT_MAX_RETRIES = 2;
|
|
20
|
+
export const SYSTEM_ONE_MAX_RETRIES_LIMIT = 3;
|
|
21
|
+
|
|
22
|
+
/** Room left before the engine watchdog fires so the executor can report its own failure. */
|
|
23
|
+
const DEADLINE_MARGIN_MS = 250;
|
|
24
|
+
const BACKOFF_BASE_MS = 500;
|
|
25
|
+
const BACKOFF_CAP_MS = 8_000;
|
|
26
|
+
const MAX_RESPONSE_CHARS = 1_048_576;
|
|
27
|
+
const MAX_ERROR_BODY_CHARS = 65_536;
|
|
28
|
+
|
|
29
|
+
export interface SystemOneTransportOptions {
|
|
30
|
+
/** Transport. Tests inject a fake; production uses the global `fetch`. */
|
|
31
|
+
fetch?: typeof fetch;
|
|
32
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
33
|
+
now?: () => number;
|
|
34
|
+
random?: () => number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SystemOneRequest {
|
|
38
|
+
providerId: SystemOneHostId;
|
|
39
|
+
capability: SystemOneCapability;
|
|
40
|
+
endpoint: string;
|
|
41
|
+
apiKey: string;
|
|
42
|
+
/** The secret name the key was read under, named when the host rejects it. */
|
|
43
|
+
keyName: string;
|
|
44
|
+
body: string;
|
|
45
|
+
timeoutMs: number;
|
|
46
|
+
maxRetries: number;
|
|
47
|
+
env: NodeJS.ProcessEnv;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type SystemOneSendResult =
|
|
51
|
+
| { ok: true; text: string; requestId?: string }
|
|
52
|
+
| { ok: false; error: string };
|
|
53
|
+
|
|
54
|
+
type Attempt =
|
|
55
|
+
| { kind: "ok"; text: string; requestId?: string }
|
|
56
|
+
| { kind: "timeout" }
|
|
57
|
+
| { kind: "fail"; message: string; retryable: boolean; retryAfterMs?: number };
|
|
58
|
+
|
|
59
|
+
const REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
60
|
+
const ERROR_CODE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
61
|
+
|
|
62
|
+
function readRequestId(headers: Headers): string | undefined {
|
|
63
|
+
// Cloudflare marks every response with `cf-ray` and sends no request id.
|
|
64
|
+
const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
|
|
65
|
+
return value && REQUEST_ID_RE.test(value) ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Retry-After as delta-seconds or an HTTP date, in ms. Undefined when absent or unparseable. */
|
|
69
|
+
function parseRetryAfter(value: string | null, now: number): number | undefined {
|
|
70
|
+
if (!value) return undefined;
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1000);
|
|
73
|
+
const at = Date.parse(trimmed);
|
|
74
|
+
return Number.isNaN(at) ? undefined : Math.max(0, at - now);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Only a short identifier from the provider's error envelope. Never its message or echoed request. */
|
|
78
|
+
function readErrorCode(text: string): string | undefined {
|
|
79
|
+
if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS) return undefined;
|
|
80
|
+
let parsed: unknown;
|
|
81
|
+
try {
|
|
82
|
+
parsed = JSON.parse(text);
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(parsed)) return undefined;
|
|
87
|
+
const nested = isRecord(parsed.error) ? parsed.error : undefined;
|
|
88
|
+
const detail = isRecord(parsed.detail) ? parsed.detail : undefined;
|
|
89
|
+
for (const candidate of [
|
|
90
|
+
nested?.code,
|
|
91
|
+
nested?.type,
|
|
92
|
+
detail?.error_type,
|
|
93
|
+
parsed.code,
|
|
94
|
+
parsed.type,
|
|
95
|
+
]) {
|
|
96
|
+
if (typeof candidate === "string" && ERROR_CODE_RE.test(candidate)) return candidate;
|
|
97
|
+
}
|
|
98
|
+
// Cloudflare's envelope: `{ success: false, errors: [{ code: 10000, message }] }`.
|
|
99
|
+
return cloudflareErrorCode(parsed);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function defaultSleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
if (signal.aborted) return resolve();
|
|
105
|
+
const timer = setTimeout(done, ms);
|
|
106
|
+
function done() {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
signal.removeEventListener("abort", done);
|
|
109
|
+
resolve();
|
|
110
|
+
}
|
|
111
|
+
signal.addEventListener("abort", done, { once: true });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* POST once, retrying only transient failures inside one time budget.
|
|
117
|
+
* Retries: connection errors, HTTP 408 / 429 / 5xx (incl. 529). Never: 401, 422,
|
|
118
|
+
* other 4xx, redirects, or a bad success body (the caller validates that).
|
|
119
|
+
*/
|
|
120
|
+
export async function sendSystemOneRequest(
|
|
121
|
+
request: SystemOneRequest,
|
|
122
|
+
options: SystemOneTransportOptions = {},
|
|
123
|
+
): Promise<SystemOneSendResult> {
|
|
124
|
+
const { timeoutMs, maxRetries } = request;
|
|
125
|
+
const doFetch = options.fetch ?? fetch;
|
|
126
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
127
|
+
const now = options.now ?? Date.now;
|
|
128
|
+
const random = options.random ?? Math.random;
|
|
129
|
+
|
|
130
|
+
const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
|
|
133
|
+
const timedOut = () =>
|
|
134
|
+
({
|
|
135
|
+
ok: false,
|
|
136
|
+
error: `SystemOne request exceeded its ${timeoutMs}ms time budget`,
|
|
137
|
+
}) as const;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
for (let attempt = 0; ; attempt++) {
|
|
141
|
+
const result = await attemptOnce(doFetch, request, controller.signal, now);
|
|
142
|
+
if (result.kind === "ok") {
|
|
143
|
+
return { ok: true, text: result.text, requestId: result.requestId };
|
|
144
|
+
}
|
|
145
|
+
if (result.kind === "timeout" || controller.signal.aborted) return timedOut();
|
|
146
|
+
|
|
147
|
+
const attempts = attempt + 1;
|
|
148
|
+
const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
|
|
149
|
+
if (!result.retryable || attempt >= maxRetries) {
|
|
150
|
+
return { ok: false, error: `${result.message}${suffix}` };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
|
|
154
|
+
const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
|
|
155
|
+
if (delay >= deadlineAt - now()) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
await sleep(delay, controller.signal);
|
|
162
|
+
if (controller.signal.aborted) return timedOut();
|
|
163
|
+
}
|
|
164
|
+
} finally {
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function attemptOnce(
|
|
170
|
+
doFetch: typeof fetch,
|
|
171
|
+
request: SystemOneRequest,
|
|
172
|
+
signal: AbortSignal,
|
|
173
|
+
now: () => number,
|
|
174
|
+
): Promise<Attempt> {
|
|
175
|
+
const { endpoint, apiKey, body } = request;
|
|
176
|
+
let res: Response;
|
|
177
|
+
try {
|
|
178
|
+
res = await doFetch(endpoint, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
...getOpenRouterAttributionHeaders(endpoint, request.env),
|
|
182
|
+
"content-type": "application/json",
|
|
183
|
+
accept: "application/json",
|
|
184
|
+
authorization: `Bearer ${apiKey}`,
|
|
185
|
+
},
|
|
186
|
+
body,
|
|
187
|
+
redirect: "manual",
|
|
188
|
+
signal,
|
|
189
|
+
});
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (signal.aborted) return { kind: "timeout" };
|
|
192
|
+
const code = (err as { code?: unknown })?.code;
|
|
193
|
+
const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
|
|
194
|
+
return {
|
|
195
|
+
kind: "fail",
|
|
196
|
+
message: `SystemOne request failed: network error${detail}`,
|
|
197
|
+
retryable: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const requestId = readRequestId(res.headers);
|
|
202
|
+
const withId = (message: string) => (requestId ? `${message} [request ${requestId}]` : message);
|
|
203
|
+
|
|
204
|
+
if (res.status >= 200 && res.status < 300) {
|
|
205
|
+
let text: string;
|
|
206
|
+
try {
|
|
207
|
+
text = await res.text();
|
|
208
|
+
} catch {
|
|
209
|
+
if (signal.aborted) return { kind: "timeout" };
|
|
210
|
+
return {
|
|
211
|
+
kind: "fail",
|
|
212
|
+
message: withId("SystemOne response body could not be read"),
|
|
213
|
+
retryable: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (text.length > MAX_RESPONSE_CHARS) {
|
|
217
|
+
return {
|
|
218
|
+
kind: "fail",
|
|
219
|
+
message: withId("SystemOne response exceeded the size limit"),
|
|
220
|
+
retryable: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return { kind: "ok", text, requestId };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (res.status >= 300 && res.status < 400) {
|
|
227
|
+
return {
|
|
228
|
+
kind: "fail",
|
|
229
|
+
message: withId(
|
|
230
|
+
`SystemOne request was redirected (HTTP ${res.status}); redirects are refused`,
|
|
231
|
+
),
|
|
232
|
+
retryable: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let errorText = "";
|
|
237
|
+
try {
|
|
238
|
+
errorText = await res.text();
|
|
239
|
+
} catch {
|
|
240
|
+
if (signal.aborted) return { kind: "timeout" };
|
|
241
|
+
}
|
|
242
|
+
const code = readErrorCode(errorText);
|
|
243
|
+
// The host answered but does not accept the key: name it, never retry.
|
|
244
|
+
if (res.status === 401 || res.status === 403) {
|
|
245
|
+
return {
|
|
246
|
+
kind: "fail",
|
|
247
|
+
message: withId(
|
|
248
|
+
systemOneKeyRejectedMessage(request.providerId, res.status, code, {
|
|
249
|
+
keyName: request.keyName,
|
|
250
|
+
capability: request.capability,
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
253
|
+
retryable: false,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const detail = code ? ` (${code})` : "";
|
|
257
|
+
const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
|
|
258
|
+
return {
|
|
259
|
+
kind: "fail",
|
|
260
|
+
message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
|
|
261
|
+
retryable,
|
|
262
|
+
retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
@@ -11,5 +11,5 @@
|
|
|
11
11
|
## Tracked Items
|
|
12
12
|
|
|
13
13
|
- Live items only: active blockers and watch items. Cap 10, 20 max during a surge; prune before you add.
|
|
14
|
-
- Each item states its lift trigger and a calendar date (e.g. `lift when X; check
|
|
14
|
+
- Each item states its lift trigger and a calendar date as a bare `MM-DD` (e.g. `lift when X; check 08-22`) or `DELETE MM-DD`. A full `YYYY-MM-DD` stamp does not arm the gate.
|
|
15
15
|
- An item closes: move its durable facts to memory, then delete the line. Never strike it through.
|
|
@@ -645,6 +645,8 @@ Only inside action-chain `params` (`on.<event>`, `Table.rowActions[].actions`, o
|
|
|
645
645
|
| `{ "$form": "title" }` | One current Form value. |
|
|
646
646
|
| `{ "$form": "" }` | All current Form values. |
|
|
647
647
|
| `{ "$state": "/queries/allIdeas/data" }` | Current value at a valid app state path, including action state such as `/actions/refreshIssues/status`. |
|
|
648
|
+
| `{ "$chain": "leafTaskId" }` | One chain-scope value, published by a component that runs a task chain; it carries `leafTaskId` and `newTaskId`. Where no chain scope is published, the sentinel is left in place and the action fails instead of silently dropping the parent. |
|
|
649
|
+
| `{ "$chain": "" }` | The whole chain scope. |
|
|
648
650
|
|
|
649
651
|
Each sentinel object must contain exactly the single key shown.
|
|
650
652
|
|
|
@@ -652,7 +654,7 @@ An action-chain step is `{ "action": "<type>", "params": {...} }`. Available act
|
|
|
652
654
|
|
|
653
655
|
- `app.mutate`: `{ model, op, rowId?, values?, formId? }`, where `op` is `"create"`, `"update"`, or `"delete"`. `update`/`delete` require `rowId` (usually `{ "$row": "id" }`, or `{ "$state": "/route/params/<param>" }` in a Drawer footer); literal `values` keys must be model columns. Successful mutation refetches all queries on that model.
|
|
654
656
|
- `app.refresh`: `{ query? }`; omit `query` to refetch all, or name a declared query.
|
|
655
|
-
- `app.action`: `{ name, input? }`; `name` must be declared in definition `actions`. For a task-kind row action, pass whole-row context with `input: { issue: { "$row": "" } }`.
|
|
657
|
+
- `app.action`: `{ name, input?, parentTaskId? }`; `name` must be declared in definition `actions`. For a task-kind row action, pass whole-row context with `input: { issue: { "$row": "" } }`. `parentTaskId` chains the created task under an existing one for task-kind actions (typically `{ "$chain": "leafTaskId" }`; a literal or `$state` task id also works); a script or sync action rejects it with 400.
|
|
656
658
|
- `app.navigate`: `{ page, params? }`; `page` must be declared, supplied keys must be params of the target page, and every required target param must be supplied. `params` replace the current route params wholesale and may contain action sentinels. Navigation pushes history, so browser Back returns to the previous page/params; only `mode` is preserved automatically.
|
|
657
659
|
- `ui.toast`: `{ message, description?, tone?, undo? }`, where `tone` is `"success"`, `"error"`, `"info"` (default), or `"warning"`. `undo: { label?, actions }` adds an Undo button (default label `Undo`) that runs `actions` as a chain; its steps are validated like any chain. Put it after the step it confirms, e.g. a row delete followed by `{ "action": "ui.toast", "params": { "message": "Order deleted", "undo": { "actions": [{ "action": "app.mutate", "params": { "model": "order", "op": "create", "values": { "title": { "$row": "title" } } } }] } } }`. Undo params resolve `$state` when Undo is pressed; `$row` / `$form` resolve when the toast is shown.
|
|
658
660
|
- `swarm.sdk`: `{ sdk, args? }`; invokes a catalog-supported Swarm browser SDK method with the viewer's bearer.
|
|
@@ -640,6 +640,8 @@ Only inside action-chain `params` (`on.<event>`, `Table.rowActions[].actions`, o
|
|
|
640
640
|
| `{ "$form": "title" }` | One current Form value. |
|
|
641
641
|
| `{ "$form": "" }` | All current Form values. |
|
|
642
642
|
| `{ "$state": "/queries/allIdeas/data" }` | Current value at a valid app state path, including action state such as `/actions/refreshIssues/status`. |
|
|
643
|
+
| `{ "$chain": "leafTaskId" }` | One chain-scope value, published by a component that runs a task chain; it carries `leafTaskId` and `newTaskId`. Where no chain scope is published, the sentinel is left in place and the action fails instead of silently dropping the parent. |
|
|
644
|
+
| `{ "$chain": "" }` | The whole chain scope. |
|
|
643
645
|
|
|
644
646
|
Each sentinel object must contain exactly the single key shown.
|
|
645
647
|
|
|
@@ -647,7 +649,7 @@ An action-chain step is `{ "action": "<type>", "params": {...} }`. Available act
|
|
|
647
649
|
|
|
648
650
|
- `app.mutate`: `{ model, op, rowId?, values?, formId? }`, where `op` is `"create"`, `"update"`, or `"delete"`. `update`/`delete` require `rowId` (usually `{ "$row": "id" }`, or `{ "$state": "/route/params/<param>" }` in a Drawer footer); literal `values` keys must be model columns. Successful mutation refetches all queries on that model.
|
|
649
651
|
- `app.refresh`: `{ query? }`; omit `query` to refetch all, or name a declared query.
|
|
650
|
-
- `app.action`: `{ name, input? }`; `name` must be declared in definition `actions`. For a task-kind row action, pass whole-row context with `input: { issue: { "$row": "" } }`.
|
|
652
|
+
- `app.action`: `{ name, input?, parentTaskId? }`; `name` must be declared in definition `actions`. For a task-kind row action, pass whole-row context with `input: { issue: { "$row": "" } }`. `parentTaskId` chains the created task under an existing one for task-kind actions (typically `{ "$chain": "leafTaskId" }`; a literal or `$state` task id also works); a script or sync action rejects it with 400.
|
|
651
653
|
- `app.navigate`: `{ page, params? }`; `page` must be declared, supplied keys must be params of the target page, and every required target param must be supplied. `params` replace the current route params wholesale and may contain action sentinels. Navigation pushes history, so browser Back returns to the previous page/params; only `mode` is preserved automatically.
|
|
652
654
|
- `ui.toast`: `{ message, description?, tone?, undo? }`, where `tone` is `"success"`, `"error"`, `"info"` (default), or `"warning"`. `undo: { label?, actions }` adds an Undo button (default label `Undo`) that runs `actions` as a chain; its steps are validated like any chain. Put it after the step it confirms, e.g. a row delete followed by `{ "action": "ui.toast", "params": { "message": "Order deleted", "undo": { "actions": [{ "action": "app.mutate", "params": { "model": "order", "op": "create", "values": { "title": { "$row": "title" } } } }] } } }`. Undo params resolve `$state` when Undo is pressed; `$row` / `$form` resolve when the toast is shown.
|
|
653
655
|
- `swarm.sdk`: `{ sdk, args? }`; invokes a catalog-supported Swarm browser SDK method with the viewer's bearer.
|
|
@@ -22,7 +22,7 @@ Two kinds of sections:
|
|
|
22
22
|
## The cap
|
|
23
23
|
|
|
24
24
|
- Tracked items across all tracked sections: 10. Absolute maximum 20, only during a real surge.
|
|
25
|
-
- Every tracked item states its lift trigger and a date. An item with no removal condition does not go in.
|
|
25
|
+
- Every tracked item states its lift trigger and a date. An item with no removal condition does not go in. Write the date as a bare `MM-DD` (`check 10-20`) or `DELETE MM-DD` for a past-date prune: those are the only forms the gate can see.
|
|
26
26
|
- Write an age trigger as a calendar date (opened date + N days), never as a day count computed at check time. A floored age fires a day late.
|
|
27
27
|
- Never arm a defer chain to re-check a tracked item. The next heartbeat already checks it. Defer only for work no scheduled pass owns.
|
|
28
28
|
- Incident detail goes to memory: `memory-store` at `swarm` scope, one line of pointer stays in the runbook.
|
|
@@ -63,7 +63,7 @@ A gate script can run before each checklist tick and decide whether the tick nee
|
|
|
63
63
|
- `HEARTBEAT_CHECKLIST_GATE_MODE=enforce`: a quiet verdict skips the task. A wake verdict adds the gate's summary as a Gate Findings section. Enforce is opt-in; turn it on only after shadow tags agree with your own quiet outcomes.
|
|
64
64
|
- The gate fails open. A timeout (60 s), a script error or a malformed result is an `error` verdict, and the task is created in both modes.
|
|
65
65
|
- Preview a verdict with `script-run` on `heartbeat-jev-gate`, `args: { dryRun: true }`.
|
|
66
|
-
- The gate sees a tracked item as due when its line contains today's
|
|
66
|
+
- The gate sees a tracked item as due when its line contains today's `MM-DD` as a bare token (`check 10-20`) or a `DELETE MM-DD` marker on or before today. A full `YYYY-MM-DD` date does not count: a line that only carries a re-verification stamp (`2026-10-20 ~10:45Z`) is invisible to the gate, so write the due date bare. A line with no date is invisible too, and a date already past is only caught through `DELETE MM-DD`, so the prune in step 2 still matters.
|
|
67
67
|
|
|
68
68
|
## Example standing orders
|
|
69
69
|
|
|
@@ -74,5 +74,5 @@ A gate script can run before each checklist tick and decide whether the tick nee
|
|
|
74
74
|
- Slack requests older than 1 hour without a task: create the task.
|
|
75
75
|
|
|
76
76
|
## Watch Items
|
|
77
|
-
- Linear webhook retries since 2026-08-18. Lift when zero retries for 48 h (check
|
|
77
|
+
- Linear webhook retries since 2026-08-18. Lift when zero retries for 48 h (check 08-22).
|
|
78
78
|
```
|
package/dist/cli-v1sjhn52.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isUserAllowed2,
|
|
3
|
-
registerMessageHandler2
|
|
4
|
-
} from "./cli-dm8p2dpf.js";
|
|
5
|
-
import"./cli-bxrqvpg5.js";
|
|
6
|
-
import"./cli-4yef8ghv.js";
|
|
7
|
-
import"./cli-kgnkyabk.js";
|
|
8
|
-
import"./cli-smvcw9z5.js";
|
|
9
|
-
import"./cli-1eszr343.js";
|
|
10
|
-
import"./cli-qz93y3x5.js";
|
|
11
|
-
import"./cli-3h26v7yj.js";
|
|
12
|
-
import"./cli-e8xyjvfc.js";
|
|
13
|
-
import"./cli-npfe9t1s.js";
|
|
14
|
-
import"./cli-73ezfhha.js";
|
|
15
|
-
import"./cli-3ggwdtb0.js";
|
|
16
|
-
import"./cli-pygdsgh8.js";
|
|
17
|
-
import"./cli-1n9gdg56.js";
|
|
18
|
-
import"./cli-vq9gqarq.js";
|
|
19
|
-
import"./cli-qje9k2nh.js";
|
|
20
|
-
import"./cli-0dxxwfkt.js";
|
|
21
|
-
import"./cli-s9rr4gr2.js";
|
|
22
|
-
import"./cli-8nyhf7za.js";
|
|
23
|
-
import"./cli-ejs3m1sx.js";
|
|
24
|
-
import"./cli-6eyncg0y.js";
|
|
25
|
-
import"./cli-13p25grh.js";
|
|
26
|
-
import"./cli-0adyhvq2.js";
|
|
27
|
-
import"./cli-tkf4x6ha.js";
|
|
28
|
-
import"./cli-v87dr6e3.js";
|
|
29
|
-
import"./cli-gx0j2khs.js";
|
|
30
|
-
import"./cli-sds5zzs8.js";
|
|
31
|
-
import"./cli-445c1vp7.js";
|
|
32
|
-
import"./cli-kvzgak7c.js";
|
|
33
|
-
import"./cli-qp2m4btv.js";
|
|
34
|
-
import"./cli-vcgscaan.js";
|
|
35
|
-
import"./cli-wt645v7q.js";
|
|
36
|
-
export {
|
|
37
|
-
isUserAllowed2 as isUserAllowed,
|
|
38
|
-
registerMessageHandler2 as registerMessageHandler
|
|
39
|
-
};
|