@desplega.ai/agent-swarm 2.4.1 → 2.4.3
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-zvzr538d.js → acp-adapter-1885dpr7.js} +4 -4
- package/dist/{actions-jhtqdw69.js → actions-pn76n32q.js} +18 -18
- package/dist/{amp-adapter-bd84rhs2.js → amp-adapter-af84rx1d.js} +3 -3
- package/dist/{app-kreq1gwg.js → app-6sv8mhzm.js} +3 -3
- package/dist/{assistant-3k22sbsq.js → assistant-sc2tf825.js} +20 -20
- package/dist/{boot-reembed-f6z923ny.js → boot-reembed-gvxjs2py.js} +1 -1
- package/dist/{boot-reembed-e53x6b9v.js → boot-reembed-y3m0ydzj.js} +1 -1
- package/dist/{boot-scrub-sweep-0538vsb7.js → boot-scrub-sweep-1j9mk05f.js} +3 -3
- package/dist/{boot-seeds-6wxqkfk2.js → boot-seeds-37sp626b.js} +4 -4
- package/dist/{claude-adapter-gfts5044.js → claude-adapter-rp0jtnwr.js} +4 -4
- package/dist/{claude-managed-setup-h00hsgtz.js → claude-managed-setup-rdt248k7.js} +1 -1
- package/dist/{claude-sdk-session-56besmfz.js → claude-sdk-session-53rcw5we.js} +9822 -9749
- package/dist/{cli-3tnpj2bh.js → cli-038fga5k.js} +2 -2
- package/dist/{cli-kmqgmcnb.js → cli-0dxxwfkt.js} +8 -8
- package/dist/{cli-471qk6s2.js → cli-1012xb7j.js} +28 -28
- package/dist/{cli-sv5gb0yb.js → cli-1eszr343.js} +1 -1
- package/dist/{cli-7225gwj7.js → cli-1m17dc9d.js} +1 -1
- package/dist/{cli-xftrvxag.js → cli-1ma335fz.js} +3 -3
- package/dist/{cli-s48yvzb8.js → cli-1n9gdg56.js} +4 -4
- package/dist/{cli-ash6xxk7.js → cli-1xsraagq.js} +3 -3
- package/dist/{cli-xbzvet96.js → cli-36ckw0wx.js} +5 -4
- package/dist/{cli-mhywgrrz.js → cli-4sydckyj.js} +1 -1
- package/dist/{cli-3r448dqn.js → cli-4yef8ghv.js} +3 -3
- package/dist/{cli-g0hgtz4p.js → cli-58vwn100.js} +3 -3
- package/dist/{cli-pw7v69vp.js → cli-625tb7aq.js} +1 -1
- package/dist/{cli-m2qgh5sv.js → cli-6aen3ezb.js} +1 -1
- package/dist/{cli-z071fb95.js → cli-6eyncg0y.js} +6 -6
- package/dist/{cli-0bh477zn.js → cli-73ezfhha.js} +120 -12
- package/dist/{cli-x5mq1fyq.js → cli-74c1wpm0.js} +3 -3
- package/dist/{cli-b1dfr1m4.js → cli-7a6p4e65.js} +1 -1
- package/dist/{cli-fpmsqq59.js → cli-836ehxmt.js} +1 -1
- package/dist/{cli-1pa3rz69.js → cli-8vverzf7.js} +1 -1
- package/dist/{cli-rdq7tzwm.js → cli-8y99kfey.js} +11 -11
- package/dist/{cli-z1ew8vbq.js → cli-aa3tb29r.js} +10 -5
- package/dist/{cli-6ncjvx55.js → cli-aypbg9r2.js} +1 -1
- package/dist/{cli-3kh4kpqy.js → cli-bxrqvpg5.js} +6 -6
- package/dist/{cli-0hyhdkdk.js → cli-cebvbc9t.js} +4 -1
- package/dist/{cli-p6argxh9.js → cli-dm8p2dpf.js} +7 -7
- package/dist/{cli-jh0qcrga.js → cli-e4d78xsv.js} +1 -1
- package/dist/{cli-7a91w22y.js → cli-e8xyjvfc.js} +1 -1
- package/dist/{cli-axh29tyr.js → cli-f0p0f6ja.js} +10 -10
- package/dist/{cli-w9g4sz81.js → cli-gf2850q7.js} +1 -1
- package/dist/{cli-bn5kevnf.js → cli-gx0j2khs.js} +4 -4
- package/dist/{cli-gf0kfg81.js → cli-h0mgxann.js} +5 -5
- package/dist/{cli-kr2pmfqx.js → cli-kgnkyabk.js} +85 -71
- package/dist/{cli-hqhzg1qq.js → cli-mzkwkbdy.js} +1 -1
- package/dist/{cli-k9z4zd4p.js → cli-npfe9t1s.js} +2 -2
- package/dist/{cli-syhq378v.js → cli-p0qerd2r.js} +1 -1
- package/dist/{cli-sjyetj21.js → cli-pygdsgh8.js} +1 -1
- package/dist/{cli-2wag8973.js → cli-qeaq3j6t.js} +1 -1
- package/dist/{cli-nx462e4v.js → cli-qp2m4btv.js} +3 -3
- package/dist/{cli-tbhwrxpt.js → cli-s9rr4gr2.js} +5 -5
- package/dist/{cli-fz9kewbh.js → cli-sds5zzs8.js} +1 -1
- package/dist/{cli-vy0fay6k.js → cli-tkf4x6ha.js} +15 -15
- package/dist/{cli-mfe7f7b5.js → cli-v87dr6e3.js} +2 -2
- package/dist/{cli-rqqsfz30.js → cli-vcgscaan.js} +91 -21
- package/dist/{cli-zfg9enh9.js → cli-vfha21c9.js} +6 -1
- package/dist/{cli-3p8fqcnh.js → cli-vq9gqarq.js} +1 -1
- package/dist/{cli-a4gvwttf.js → cli-wk28hzqn.js} +2 -2
- package/dist/{cli-p0rakbrk.js → cli-zvbb4n0e.js} +6 -5
- package/dist/cli.js +17 -17
- package/dist/{codex-adapter-p6rmm7vv.js → codex-adapter-2zrmab9b.js} +2 -2
- package/dist/{codex-hook-x7nejyrr.js → codex-hook-e2he7aca.js} +2 -2
- package/dist/{codex-session-runner-8vd5t9t0.js → codex-session-runner-kbkz62vc.js} +2 -2
- package/dist/{commands-7ems2p13.js → commands-hd574th6.js} +3 -3
- package/dist/{controller-m263fg43.js → controller-pz5mcfg4.js} +3 -3
- package/dist/{controllers-connect-bg33ae53.js → controllers-connect-5rgq2hfk.js} +19 -19
- package/dist/{cursor-adapter-cz6nm6mj.js → cursor-adapter-mgaprq0j.js} +2 -2
- package/dist/{db-w505dwa8.js → db-6nf1207q.js} +6 -2
- package/dist/{db-ys2rbdft.js → db-h06dxp05.js} +3 -3
- package/dist/{dispatcher-0s0mckqr.js → dispatcher-zthpy4m1.js} +17 -17
- package/dist/{dsh-adapter-3n3net33.js → dsh-adapter-64dhw0j7.js} +2 -2
- package/dist/{e2b-thdcxtjd.js → e2b-16d6ch39.js} +1 -1
- package/dist/{e2b-7am6tmq6.js → e2b-zn0b4w5w.js} +1 -1
- package/dist/{external-channel-5b23fv8z.js → external-channel-x01t7ke6.js} +2 -2
- package/dist/{grok-adapter-q9fghe5q.js → grok-adapter-h21jv594.js} +5 -5
- package/dist/handlers-2qjw434w.js +39 -0
- package/dist/{heartbeat-tww6yx1w.js → heartbeat-zzfksckz.js} +20 -20
- package/dist/{hook-pvmh9b6m.js → hook-3xfqhh2s.js} +2 -2
- package/dist/{hook-dx0pkvar.js → hook-z4zhzj1f.js} +10 -5
- package/dist/{http-k2ejbt05.js → http-q813tmya.js} +121 -75
- package/dist/{index-2z710v4x.js → index-220tk9bw.js} +9 -9
- package/dist/index-d0g18bax.js +41 -0
- package/dist/{index-dgz61nff.js → index-m0a4ydqj.js} +17 -17
- package/dist/{index-h2ek08d6.js → index-wbbs14c5.js} +17 -17
- package/dist/{index-x5aktmp8.js → index-x8xbyn1t.js} +1 -1
- package/dist/{keepalive-nhvpw10z.js → keepalive-y5t1my7m.js} +3 -3
- package/dist/{lead-jb6sf69z.js → lead-2xwtq66b.js} +30 -30
- package/dist/{maintenance-agakc519.js → maintenance-ybfsaedv.js} +1 -1
- package/dist/{oauth-refresh-sweep-4dcbhtn0.js → oauth-refresh-sweep-909jmn12.js} +2 -2
- package/dist/{onboard-4n2kwa4v.js → onboard-xn6fkkys.js} +3 -3
- package/dist/{opencode-adapter-3s7ax02e.js → opencode-adapter-x4tqzhkh.js} +3 -3
- package/dist/{otel-impl-tfa12080.js → otel-impl-e6mn0z13.js} +1 -1
- package/dist/{pi-mono-adapter-ytp7gz8b.js → pi-mono-adapter-mcfkn9vf.js} +3 -3
- package/dist/{pricing-refresh-3cqx3f3x.js → pricing-refresh-ygd362ep.js} +3 -3
- package/dist/{rbac-roles-9fae5rnv.js → rbac-roles-c9axvfnz.js} +3 -3
- package/dist/{rbac-roles-y6zbfzve.js → rbac-roles-mb470p4e.js} +2 -2
- package/dist/{render-v2-emawjvc4.js → render-v2-g0306nh1.js} +3 -3
- package/dist/{resume-zhgts7h7.js → resume-v315hnq6.js} +5 -5
- package/dist/{session-access-cm823e06.js → session-access-2zg285a1.js} +4 -4
- package/dist/{setup-tkp99w1e.js → setup-a4mtf02k.js} +1 -1
- package/dist/{work-objects-f722h263.js → work-objects-9fr0fgtn.js} +21 -21
- package/dist/{worker-p7f336rq.js → worker-qae22jeb.js} +30 -30
- package/openapi.json +393 -1
- package/package.json +3 -3
- package/src/apps/catalog.generated.json +3 -2
- package/src/apps/definition.ts +152 -4
- package/src/apps/row-store.ts +19 -7
- package/src/apps/schema-migrate.ts +14 -6
- package/src/apps/script-types.ts +1 -0
- package/src/apps/sync.ts +20 -19
- package/src/be/db/tasks/read.ts +112 -0
- package/src/be/db.ts +9 -4
- package/src/be/migrations/231_slack_tasks_last_updated_index.sql +5 -0
- package/src/be/swarm-config-guard.ts +15 -0
- package/src/github/handlers.ts +6 -5
- package/src/hooks/hook.ts +2 -1
- package/src/http/apps.ts +15 -2
- package/src/http/status.ts +49 -43
- package/src/http/tasks.ts +47 -0
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +4 -4
- package/src/providers/opencode-adapter.ts +2 -2
- package/src/providers/pi-mono-extension.ts +2 -1
- package/src/telemetry.ts +15 -3
- package/src/tests/app-row-tools.test.ts +25 -0
- package/src/tests/apps-json-column.test.ts +611 -0
- package/src/tests/apps-script-types.test.ts +2 -0
- package/src/tests/apps-sync-engine.test.ts +42 -0
- package/src/tests/claude-stop-hook.test.ts +47 -0
- package/src/tests/codex-adapter.test.ts +42 -0
- package/src/tests/db-task-read-contract.test.ts +4 -0
- package/src/tests/db-task-write-contract.test.ts +2 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/env-flag.test.ts +15 -1
- package/src/tests/github-handlers-cancel-config.test.ts +29 -8
- package/src/tests/github-handlers-inline-comments.test.ts +9 -2
- package/src/tests/runner-harness-telemetry-env.test.ts +6 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/skill-script-refs.test.ts +63 -0
- package/src/tests/slack-render-v2-query-plans.test.ts +147 -0
- package/src/tests/status.test.ts +158 -9
- package/src/tests/task-lineage.test.ts +180 -0
- package/src/tests/telemetry-init.test.ts +62 -5
- package/src/tests/user-token-rest-auth.test.ts +38 -0
- package/src/tools/app-rows.ts +5 -0
- package/src/utils/env-flag.ts +9 -0
- package/templates/skills/apps/SKILL.md +4 -2
- package/templates/skills/apps/content.md +4 -2
- package/templates/skills/code-quality/content.md +9 -14
- package/dist/handlers-mqrym347.js +0 -39
- package/dist/index-pqfwh9nn.js +0 -41
|
@@ -371,6 +371,53 @@ describe("runStopHookSessionSummary", () => {
|
|
|
371
371
|
expect(consoleErrors.length).toBe(0);
|
|
372
372
|
});
|
|
373
373
|
|
|
374
|
+
test.each(["true", "TRUE"])("SKIP_SESSION_SUMMARY=%s → no runSummarize call", async (value) => {
|
|
375
|
+
const transcriptPath = await writeTempTranscript(longTranscript());
|
|
376
|
+
|
|
377
|
+
let runSummarizeCalled = false;
|
|
378
|
+
const deps: RunStopHookSessionSummaryDeps = {
|
|
379
|
+
runSummarize: async () => {
|
|
380
|
+
runSummarizeCalled = true;
|
|
381
|
+
throw new Error("should not be called");
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
await runStopHookSessionSummary(
|
|
386
|
+
{
|
|
387
|
+
agentId: "agent-claude-1",
|
|
388
|
+
transcriptPath,
|
|
389
|
+
env: makeEnv({ SKIP_SESSION_SUMMARY: value }),
|
|
390
|
+
},
|
|
391
|
+
deps,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(runSummarizeCalled).toBe(false);
|
|
395
|
+
expect(fetchCalls.length).toBe(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test.each(["0", "false"])("SKIP_SESSION_SUMMARY=%s → summary still runs", async (value) => {
|
|
399
|
+
const transcriptPath = await writeTempTranscript(longTranscript());
|
|
400
|
+
|
|
401
|
+
let runSummarizeCalled = false;
|
|
402
|
+
const deps: RunStopHookSessionSummaryDeps = {
|
|
403
|
+
runSummarize: async () => {
|
|
404
|
+
runSummarizeCalled = true;
|
|
405
|
+
return null;
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
await runStopHookSessionSummary(
|
|
410
|
+
{
|
|
411
|
+
agentId: "agent-claude-1",
|
|
412
|
+
transcriptPath,
|
|
413
|
+
env: makeEnv({ SKIP_SESSION_SUMMARY: value }),
|
|
414
|
+
},
|
|
415
|
+
deps,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
expect(runSummarizeCalled).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
|
|
374
421
|
test("short transcript (≤100 chars) → no runSummarize call, no POST", async () => {
|
|
375
422
|
const transcriptPath = await writeTempTranscript("tiny");
|
|
376
423
|
|
|
@@ -2294,6 +2294,48 @@ describe("CodexSession session-end summarization", () => {
|
|
|
2294
2294
|
expect(indexCalls.length).toBe(0);
|
|
2295
2295
|
});
|
|
2296
2296
|
|
|
2297
|
+
test.each(["true", "TRUE"])("SKIP_SESSION_SUMMARY=%s → no runSummarize call", async (value) => {
|
|
2298
|
+
process.env.SKIP_SESSION_SUMMARY = value;
|
|
2299
|
+
const events = buildSummaryEvents();
|
|
2300
|
+
|
|
2301
|
+
let runSummarizeCalls = 0;
|
|
2302
|
+
const deps: SummarizeSessionForCodexDeps = {
|
|
2303
|
+
runSummarize: async () => {
|
|
2304
|
+
runSummarizeCalls += 1;
|
|
2305
|
+
return null;
|
|
2306
|
+
},
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
const config = testConfig({
|
|
2310
|
+
logFile: join(tmpLogDir, `skip-${value}.log`),
|
|
2311
|
+
cwd: "",
|
|
2312
|
+
});
|
|
2313
|
+
await runSessionWithFakeThreadAndDeps(events, config, deps);
|
|
2314
|
+
|
|
2315
|
+
expect(runSummarizeCalls).toBe(0);
|
|
2316
|
+
});
|
|
2317
|
+
|
|
2318
|
+
test.each(["0", "false"])("SKIP_SESSION_SUMMARY=%s → summary still runs", async (value) => {
|
|
2319
|
+
process.env.SKIP_SESSION_SUMMARY = value;
|
|
2320
|
+
const events = buildSummaryEvents();
|
|
2321
|
+
|
|
2322
|
+
let runSummarizeCalls = 0;
|
|
2323
|
+
const deps: SummarizeSessionForCodexDeps = {
|
|
2324
|
+
runSummarize: async () => {
|
|
2325
|
+
runSummarizeCalls += 1;
|
|
2326
|
+
return null;
|
|
2327
|
+
},
|
|
2328
|
+
};
|
|
2329
|
+
|
|
2330
|
+
const config = testConfig({
|
|
2331
|
+
logFile: join(tmpLogDir, `no-skip-${value}.log`),
|
|
2332
|
+
cwd: "",
|
|
2333
|
+
});
|
|
2334
|
+
await runSessionWithFakeThreadAndDeps(events, config, deps);
|
|
2335
|
+
|
|
2336
|
+
expect(runSummarizeCalls).toBe(1);
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2297
2339
|
test("missing taskId → no runSummarize call, no POST", async () => {
|
|
2298
2340
|
const events = buildSummaryEvents();
|
|
2299
2341
|
|
|
@@ -111,6 +111,7 @@ value:ReservedAgentRoleError
|
|
|
111
111
|
value:SESSION_ACTIVITY_REFRESH_MIN_INTERVAL_MS
|
|
112
112
|
value:SKILL_FILE_LIMITS
|
|
113
113
|
value:SUPERSEDED_RESUME_REPAIR_WINDOW_MS
|
|
114
|
+
value:TASK_LINEAGE_LIMITS
|
|
114
115
|
value:UNATTRIBUTED_USER_ID
|
|
115
116
|
value:__resetSqliteVecExtensionPathCacheForTests
|
|
116
117
|
value:abandonSlackOutcomeDelivery
|
|
@@ -412,6 +413,7 @@ value:getSystemDefaultSkills
|
|
|
412
413
|
value:getTaskAttachments
|
|
413
414
|
value:getTaskById
|
|
414
415
|
value:getTaskByWorkflowRunStepId
|
|
416
|
+
value:getTaskLineage
|
|
415
417
|
value:getTaskStats
|
|
416
418
|
value:getTasksByAgentId
|
|
417
419
|
value:getTasksByStatus
|
|
@@ -612,6 +614,7 @@ type:ExistingTrackerContextWorkReason
|
|
|
612
614
|
type:PendingChatRelay
|
|
613
615
|
type:TaskFilters
|
|
614
616
|
value:SUPERSEDED_RESUME_REPAIR_WINDOW_MS
|
|
617
|
+
value:TASK_LINEAGE_LIMITS
|
|
615
618
|
value:findCompletedTaskInThread
|
|
616
619
|
value:findExistingLinearTrackerContextWork
|
|
617
620
|
value:findRecentCancelledTaskInThread
|
|
@@ -639,6 +642,7 @@ value:getSlackTasksMissingTree
|
|
|
639
642
|
value:getSupersededTasksWithUnsettledDependents
|
|
640
643
|
value:getSupersededTasksWithoutResume
|
|
641
644
|
value:getTaskById
|
|
645
|
+
value:getTaskLineage
|
|
642
646
|
value:getTaskStats
|
|
643
647
|
value:getTasksByAgentId
|
|
644
648
|
value:getTasksByStatus
|
|
@@ -134,6 +134,7 @@ value:ReservedAgentRoleError
|
|
|
134
134
|
value:SESSION_ACTIVITY_REFRESH_MIN_INTERVAL_MS
|
|
135
135
|
value:SKILL_FILE_LIMITS
|
|
136
136
|
value:SUPERSEDED_RESUME_REPAIR_WINDOW_MS
|
|
137
|
+
value:TASK_LINEAGE_LIMITS
|
|
137
138
|
value:UNATTRIBUTED_USER_ID
|
|
138
139
|
value:__resetSqliteVecExtensionPathCacheForTests
|
|
139
140
|
value:abandonSlackOutcomeDelivery
|
|
@@ -435,6 +436,7 @@ value:getSystemDefaultSkills
|
|
|
435
436
|
value:getTaskAttachments
|
|
436
437
|
value:getTaskById
|
|
437
438
|
value:getTaskByWorkflowRunStepId
|
|
439
|
+
value:getTaskLineage
|
|
438
440
|
value:getTaskStats
|
|
439
441
|
value:getTasksByAgentId
|
|
440
442
|
value:getTasksByStatus
|
|
@@ -103,8 +103,8 @@ describe("docs API index footprint", () => {
|
|
|
103
103
|
expect(spec.paths["/api/agents/{id}/runtime"]?.get).toBeDefined();
|
|
104
104
|
|
|
105
105
|
const generatedKeys = await generatedOperationKeys();
|
|
106
|
-
expect(operationKeys).toHaveLength(
|
|
107
|
-
expect(generatedKeys).toHaveLength(
|
|
106
|
+
expect(operationKeys).toHaveLength(437);
|
|
107
|
+
expect(generatedKeys).toHaveLength(437);
|
|
108
108
|
expect(new Set(generatedKeys)).toEqual(new Set(operationKeys));
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
3
|
-
import { isEnvFlagEnabled, parseEnvFlag } from "../utils/env-flag";
|
|
3
|
+
import { isEnvFlagEnabled, isSessionSummarySkipped, parseEnvFlag } from "../utils/env-flag";
|
|
4
4
|
|
|
5
5
|
describe("parseEnvFlag", () => {
|
|
6
6
|
test("accepts both truthy serializations", () => {
|
|
@@ -199,3 +199,17 @@ describe("swarm-config-guard: Configuration-page value validation", () => {
|
|
|
199
199
|
expect(validateConfigValue("SOME_RANDOM_KEY", "whatever")).toBeNull();
|
|
200
200
|
});
|
|
201
201
|
});
|
|
202
|
+
|
|
203
|
+
describe("isSessionSummarySkipped", () => {
|
|
204
|
+
test.each(["1", "true", "TRUE", " True "])("SKIP_SESSION_SUMMARY=%p skips", (value) => {
|
|
205
|
+
expect(isSessionSummarySkipped({ SKIP_SESSION_SUMMARY: value })).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test.each(["0", "false", "FALSE", ""])("SKIP_SESSION_SUMMARY=%p keeps the summary", (value) => {
|
|
209
|
+
expect(isSessionSummarySkipped({ SKIP_SESSION_SUMMARY: value })).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("unset keeps the summary", () => {
|
|
213
|
+
expect(isSessionSummarySkipped({})).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* github.cancelOnUnassign — PR unassigned + issue unassigned
|
|
7
7
|
* github.cancelOnReviewRequestRemoved — PR review_request_removed
|
|
8
8
|
*
|
|
9
|
-
* Absent /
|
|
10
|
-
*
|
|
9
|
+
* Absent / empty / unrecognized values → cancel (default).
|
|
10
|
+
* Values "false" / "0" (case-insensitive, trimmed) → leave task untouched.
|
|
11
11
|
*/
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
13
|
import { unlink } from "node:fs/promises";
|
|
@@ -122,8 +122,15 @@ describe("PR unassigned — github.cancelOnUnassign", () => {
|
|
|
122
122
|
expect(await getTaskStatus(taskId)).toBe("failed");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
test(
|
|
126
|
-
|
|
125
|
+
test.each([
|
|
126
|
+
"false",
|
|
127
|
+
"FALSE",
|
|
128
|
+
"False",
|
|
129
|
+
"0",
|
|
130
|
+
" false ",
|
|
131
|
+
" 0 ",
|
|
132
|
+
])("config = %j: unassign leaves task untouched", async (value) => {
|
|
133
|
+
await setConfigFlag("github.cancelOnUnassign", value);
|
|
127
134
|
const taskId = await seedTask(BASE_PR.number, "pr");
|
|
128
135
|
expect(await getTaskStatus(taskId)).toBe("pending");
|
|
129
136
|
|
|
@@ -162,8 +169,15 @@ describe("issue unassigned — github.cancelOnUnassign", () => {
|
|
|
162
169
|
expect(await getTaskStatus(taskId)).toBe("failed");
|
|
163
170
|
});
|
|
164
171
|
|
|
165
|
-
test(
|
|
166
|
-
|
|
172
|
+
test.each([
|
|
173
|
+
"false",
|
|
174
|
+
"FALSE",
|
|
175
|
+
"False",
|
|
176
|
+
"0",
|
|
177
|
+
" false ",
|
|
178
|
+
" 0 ",
|
|
179
|
+
])("config = %j: unassign leaves task untouched", async (value) => {
|
|
180
|
+
await setConfigFlag("github.cancelOnUnassign", value);
|
|
167
181
|
const taskId = await seedTask(BASE_ISSUE.number, "issue");
|
|
168
182
|
expect(await getTaskStatus(taskId)).toBe("pending");
|
|
169
183
|
|
|
@@ -201,8 +215,15 @@ describe("PR review_request_removed — github.cancelOnReviewRequestRemoved", ()
|
|
|
201
215
|
expect(await getTaskStatus(taskId)).toBe("failed");
|
|
202
216
|
});
|
|
203
217
|
|
|
204
|
-
test(
|
|
205
|
-
|
|
218
|
+
test.each([
|
|
219
|
+
"false",
|
|
220
|
+
"FALSE",
|
|
221
|
+
"False",
|
|
222
|
+
"0",
|
|
223
|
+
" false ",
|
|
224
|
+
" 0 ",
|
|
225
|
+
])("config = %j: review removal leaves task untouched", async (value) => {
|
|
226
|
+
await setConfigFlag("github.cancelOnReviewRequestRemoved", value);
|
|
206
227
|
const taskId = await seedTask(BASE_PR.number, "pr");
|
|
207
228
|
expect(await getTaskStatus(taskId)).toBe("pending");
|
|
208
229
|
|
|
@@ -573,9 +573,16 @@ describe("no-op approved reviews", () => {
|
|
|
573
573
|
}
|
|
574
574
|
});
|
|
575
575
|
|
|
576
|
-
test(
|
|
576
|
+
test.each([
|
|
577
|
+
"false",
|
|
578
|
+
"FALSE",
|
|
579
|
+
"False",
|
|
580
|
+
"0",
|
|
581
|
+
" false ",
|
|
582
|
+
" 0 ",
|
|
583
|
+
])("GITHUB_SKIP_NOOP_APPROVALS=%j: empty-body approval still creates a task", async (value) => {
|
|
577
584
|
const previous = process.env.GITHUB_SKIP_NOOP_APPROVALS;
|
|
578
|
-
process.env.GITHUB_SKIP_NOOP_APPROVALS =
|
|
585
|
+
process.env.GITHUB_SKIP_NOOP_APPROVALS = value;
|
|
579
586
|
const fetchSpy = mockFetchForApproval({ reviews: [APPROVED_BY_REVIEWER] });
|
|
580
587
|
|
|
581
588
|
try {
|
|
@@ -11,6 +11,12 @@ const TELEMETRY_PATH = join(import.meta.dir, "..", "telemetry.ts");
|
|
|
11
11
|
// leaves the process), and print the environment the event carries.
|
|
12
12
|
const BOOT_SCRIPT = `
|
|
13
13
|
const t = await import(${JSON.stringify(TELEMETRY_PATH)});
|
|
14
|
+
const { mkdtempSync, rmSync } = await import("node:fs");
|
|
15
|
+
const { tmpdir } = await import("node:os");
|
|
16
|
+
const { pathToFileURL } = await import("node:url");
|
|
17
|
+
const root = mkdtempSync(tmpdir() + "/telemetry-package-");
|
|
18
|
+
t.getTelemetryEnvironment(pathToFileURL(root + "/"));
|
|
19
|
+
rmSync(root, { recursive: true, force: true });
|
|
14
20
|
let body;
|
|
15
21
|
globalThis.fetch = async (_url, init) => {
|
|
16
22
|
body = JSON.parse(init.body);
|
|
@@ -634,6 +634,8 @@ describe("script connections", () => {
|
|
|
634
634
|
markMigrationApplied(database, "220_model_profiles.sql");
|
|
635
635
|
// 227 alters inbox_item_state, which this migration-112-only fixture does not create.
|
|
636
636
|
markMigrationApplied(database, "227_inbox_item_state_last_read_at.sql");
|
|
637
|
+
// 231 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
|
|
638
|
+
markMigrationApplied(database, "231_slack_tasks_last_updated_index.sql");
|
|
637
639
|
|
|
638
640
|
const id = crypto.randomUUID();
|
|
639
641
|
const now = new Date().toISOString();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { SEED_SCRIPTS } from "../be/seed-scripts";
|
|
4
|
+
|
|
5
|
+
// A shipped skill that tells agents to `script-run` a named script must name a
|
|
6
|
+
// script this repo ships in SEED_SCRIPTS. Otherwise the skill works only on a
|
|
7
|
+
// swarm that happens to hold that script in its catalog, and every other
|
|
8
|
+
// deployment hits "Script not found" at that step.
|
|
9
|
+
|
|
10
|
+
const REPO_ROOT = join(import.meta.dir, "../..");
|
|
11
|
+
// Seeded skills, baked plugin commands/agents/skills, and public operator skills.
|
|
12
|
+
const SKILL_DIRS = ["templates/skills", "plugin", "skills"].map((d) => join(REPO_ROOT, d));
|
|
13
|
+
|
|
14
|
+
// `script-run name="x"` and the prose forms "`script-run` with name `x`" / "`script-run` on `x`".
|
|
15
|
+
const REF_PATTERNS = [
|
|
16
|
+
/script-run\s+name=["']([a-z0-9][a-z0-9-]*)["']/g,
|
|
17
|
+
/`script-run`\s+(?:with\s+name|on)\s+`([a-z0-9][a-z0-9-]*)`/g,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function findScriptRefs(text: string): string[] {
|
|
21
|
+
const names = new Set<string>();
|
|
22
|
+
for (const re of REF_PATTERNS) {
|
|
23
|
+
for (const m of text.matchAll(re)) if (m[1]) names.add(m[1]);
|
|
24
|
+
}
|
|
25
|
+
return [...names];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function collectRefs(): Promise<{ file: string; name: string }[]> {
|
|
29
|
+
const refs: { file: string; name: string }[] = [];
|
|
30
|
+
for (const dir of SKILL_DIRS) {
|
|
31
|
+
for await (const path of new Bun.Glob("**/*.md").scan({ cwd: dir, absolute: true })) {
|
|
32
|
+
const text = await Bun.file(path).text();
|
|
33
|
+
for (const name of findScriptRefs(text)) refs.push({ file: relative(REPO_ROOT, path), name });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return refs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("shipped skills only script-run scripts the repo ships", () => {
|
|
40
|
+
test("the reference scanner matches each call form and ignores prose", () => {
|
|
41
|
+
expect(
|
|
42
|
+
findScriptRefs(
|
|
43
|
+
[
|
|
44
|
+
'Run `script-run name="internal-only-gate" scope="global" args=<draft>`.',
|
|
45
|
+
"Run `script-run` with name `boot-triage`.",
|
|
46
|
+
"Preview with `script-run` on `heartbeat-jev-gate`.",
|
|
47
|
+
"Seed scripts (`script-run` with `name` and `args`):",
|
|
48
|
+
"Inline `script-run` for a one-off.",
|
|
49
|
+
].join("\n"),
|
|
50
|
+
).sort(),
|
|
51
|
+
).toEqual(["boot-triage", "heartbeat-jev-gate", "internal-only-gate"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("every script-run name in a shipped skill is in SEED_SCRIPTS", async () => {
|
|
55
|
+
const refs = await collectRefs();
|
|
56
|
+
// Positive control: the scan must see heartbeat-runbook's boot-triage reference, or it is blind.
|
|
57
|
+
expect(refs.some((r) => r.name === "boot-triage")).toBe(true);
|
|
58
|
+
|
|
59
|
+
const shipped = new Set(SEED_SCRIPTS.map((s) => s.name));
|
|
60
|
+
const missing = refs.filter((r) => !shipped.has(r.name)).map((r) => `${r.file}: ${r.name}`);
|
|
61
|
+
expect(missing).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
closeDb,
|
|
4
|
+
createAgent,
|
|
5
|
+
createTaskExtended,
|
|
6
|
+
getDb,
|
|
7
|
+
getDbClient,
|
|
8
|
+
getResolvableDeferralOutcomes,
|
|
9
|
+
initDb,
|
|
10
|
+
recordSlackMessage,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import { getSlackTasksMissingTree } from "../be/db/tasks/read";
|
|
13
|
+
import { completeTask, startTask } from "../be/db/tasks/write";
|
|
14
|
+
|
|
15
|
+
// Both queries run on every Slack watcher tick. The DB has no sqlite_stat1, so
|
|
16
|
+
// the planner works from heuristics; these pin the plans that keep each tick
|
|
17
|
+
// proportional to Slack rows instead of every task.
|
|
18
|
+
|
|
19
|
+
async function capturedSql(run: () => Promise<unknown>): Promise<string> {
|
|
20
|
+
const client = getDbClient();
|
|
21
|
+
const seen: string[] = [];
|
|
22
|
+
const original = client.query.bind(client);
|
|
23
|
+
const spy = spyOn(client, "query").mockImplementation(((sql: string, params?: unknown[]) => {
|
|
24
|
+
seen.push(sql);
|
|
25
|
+
return original(sql, params);
|
|
26
|
+
}) as typeof client.query);
|
|
27
|
+
try {
|
|
28
|
+
await run();
|
|
29
|
+
} finally {
|
|
30
|
+
spy.mockRestore();
|
|
31
|
+
}
|
|
32
|
+
expect(seen).toHaveLength(1);
|
|
33
|
+
return seen[0]!;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function plan(sql: string, params: unknown[] = []): string {
|
|
37
|
+
return getDb()
|
|
38
|
+
.query<{ detail: string }, any[]>(`EXPLAIN QUERY PLAN ${sql}`)
|
|
39
|
+
.all(...params)
|
|
40
|
+
.map((row) => row.detail)
|
|
41
|
+
.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function deferredAsk(label: string, leadId: string) {
|
|
45
|
+
const ask = await createTaskExtended(`ask ${label}`, {
|
|
46
|
+
agentId: leadId,
|
|
47
|
+
source: "slack",
|
|
48
|
+
slackChannelId: `C_${label}`,
|
|
49
|
+
slackThreadTs: `${label}.0`,
|
|
50
|
+
});
|
|
51
|
+
await startTask(ask.id);
|
|
52
|
+
await completeTask(ask.id, "Checking back later", {
|
|
53
|
+
addTags: ["deferred"],
|
|
54
|
+
deferredAt: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
const card = await recordSlackMessage({
|
|
57
|
+
contextKey: `slack:C_${label}:${label}.0`,
|
|
58
|
+
channelId: `C_${label}`,
|
|
59
|
+
threadTs: `${label}.0`,
|
|
60
|
+
ts: `${label}.1`,
|
|
61
|
+
kind: "outcome",
|
|
62
|
+
taskId: ask.id,
|
|
63
|
+
finalized: true,
|
|
64
|
+
});
|
|
65
|
+
return { ask, card };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function wake(parentTaskId: string, leadId: string, opts: { deferAgain?: boolean } = {}) {
|
|
69
|
+
await Bun.sleep(2);
|
|
70
|
+
const task = await createTaskExtended("wake", {
|
|
71
|
+
agentId: leadId,
|
|
72
|
+
parentTaskId,
|
|
73
|
+
taskType: "deferred",
|
|
74
|
+
});
|
|
75
|
+
await startTask(task.id);
|
|
76
|
+
await completeTask(
|
|
77
|
+
task.id,
|
|
78
|
+
"done",
|
|
79
|
+
opts.deferAgain ? { deferredAt: new Date().toISOString() } : {},
|
|
80
|
+
);
|
|
81
|
+
return task;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("slack render-v2 watcher queries", () => {
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
initDb(":memory:");
|
|
87
|
+
getDb().run(
|
|
88
|
+
"INSERT OR IGNORE INTO slack_render_v2_state (id, activated_at) VALUES (1, '2000-01-01T00:00:00.000Z')",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
closeDb();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("deferral resolution drives from the chain, not from every task", async () => {
|
|
97
|
+
const sql = await capturedSql(() => getResolvableDeferralOutcomes());
|
|
98
|
+
const detail = plan(sql, [50]);
|
|
99
|
+
expect(detail).toContain(
|
|
100
|
+
"SEARCH wake USING INDEX idx_agent_tasks_parentTaskId (parentTaskId=?)",
|
|
101
|
+
);
|
|
102
|
+
expect(detail).not.toContain("idx_agent_tasks_taskType");
|
|
103
|
+
expect(detail).not.toContain("idx_agent_tasks_status_last_updated");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("missing-tree scan reads only Slack tasks", async () => {
|
|
107
|
+
const sql = await capturedSql(() => getSlackTasksMissingTree());
|
|
108
|
+
const detail = plan(sql);
|
|
109
|
+
expect(detail).toContain("SCAN task USING INDEX idx_agent_tasks_slack_last_updated");
|
|
110
|
+
expect(detail).not.toContain("idx_agent_tasks_last_updated");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("deferral resolution returns the latest terminal wake of each open chain", async () => {
|
|
114
|
+
const lead = await createAgent({ name: "Plan Lead", isLead: true, status: "idle" });
|
|
115
|
+
|
|
116
|
+
const single = await deferredAsk("SINGLE", lead.id);
|
|
117
|
+
const singleWake = await wake(single.ask.id, lead.id);
|
|
118
|
+
|
|
119
|
+
const chained = await deferredAsk("CHAIN", lead.id);
|
|
120
|
+
const firstWake = await wake(chained.ask.id, lead.id, { deferAgain: true });
|
|
121
|
+
const lastWake = await wake(firstWake.id, lead.id);
|
|
122
|
+
|
|
123
|
+
const pending = await deferredAsk("PENDING", lead.id);
|
|
124
|
+
const running = await createTaskExtended("still running", {
|
|
125
|
+
agentId: lead.id,
|
|
126
|
+
parentTaskId: pending.ask.id,
|
|
127
|
+
taskType: "deferred",
|
|
128
|
+
});
|
|
129
|
+
await startTask(running.id);
|
|
130
|
+
|
|
131
|
+
const plainChild = await deferredAsk("PLAIN", lead.id);
|
|
132
|
+
const notDeferred = await createTaskExtended("ordinary child", {
|
|
133
|
+
agentId: lead.id,
|
|
134
|
+
parentTaskId: plainChild.ask.id,
|
|
135
|
+
});
|
|
136
|
+
await startTask(notDeferred.id);
|
|
137
|
+
await completeTask(notDeferred.id, "done");
|
|
138
|
+
|
|
139
|
+
const rows = await getResolvableDeferralOutcomes();
|
|
140
|
+
expect(rows.map((row) => [row.card.id, row.deferredTaskId, row.wakeTaskId]).sort()).toEqual(
|
|
141
|
+
[
|
|
142
|
+
[single.card.id, single.ask.id, singleWake.id],
|
|
143
|
+
[chained.card.id, chained.ask.id, lastWake.id],
|
|
144
|
+
].sort(),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
});
|