@desplega.ai/agent-swarm 1.145.0 → 1.147.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/README.md +2 -1
- package/dist/{acp-adapter-580t41c0.js → acp-adapter-9n319wqc.js} +61 -6
- package/dist/{actions-m662tjng.js → actions-2vxqvpr9.js} +9 -8
- package/dist/{app-zb3rndm8.js → app-w066xfy0.js} +6 -5
- package/dist/assistant-kt31dj2h.js +236 -0
- package/dist/{boot-reembed-072x00vn.js → boot-reembed-az5rassp.js} +7 -6
- package/dist/{boot-reembed-65zz2smt.js → boot-reembed-j3mm3rfz.js} +8 -7
- package/dist/{boot-scrub-logs-yz0ams04.js → boot-scrub-logs-b6h54817.js} +4 -4
- package/dist/{claude-adapter-paj270y2.js → claude-adapter-w1z04ann.js} +8 -6
- package/dist/{claude-managed-setup-w619jncn.js → claude-managed-setup-f5pmsyrp.js} +24 -3
- package/dist/{claude-sdk-session-swt80j9z.js → claude-sdk-session-f0cc1kec.js} +8 -6
- package/dist/{cli-dt553ktb.js → cli-0b6y7kxm.js} +1 -1
- package/dist/{cli-bxdhw615.js → cli-12fz972k.js} +4 -4
- package/dist/{cli-s2f20r5w.js → cli-1th1728g.js} +2 -2
- package/dist/{cli-rs6ec3b9.js → cli-3tgwnf43.js} +3 -3
- package/dist/{cli-gxnzc6wh.js → cli-4nr988h6.js} +1 -1
- package/dist/{cli-axs8e6d4.js → cli-53s590z8.js} +1 -1
- package/dist/{cli-ygbjp3z8.js → cli-54wwve05.js} +5 -4
- package/dist/{cli-v0asr5yv.js → cli-719k9j2c.js} +2 -2
- package/dist/{cli-anktweqr.js → cli-89xfd7f9.js} +21 -47
- package/dist/{cli-q34a0e10.js → cli-8v0g1yc8.js} +1 -1
- package/dist/cli-9d8ntetj.js +42 -0
- package/dist/{cli-z9fprcrf.js → cli-atcve9y8.js} +881 -738
- package/dist/{cli-jmb7g47h.js → cli-b5bq9crk.js} +14 -31
- package/dist/cli-d8brzxjb.js +190 -0
- package/dist/{cli-gzky0gkr.js → cli-de14znh8.js} +2 -2
- package/dist/{cli-xytwff7f.js → cli-dttph1bw.js} +2 -2
- package/dist/cli-ep19k3mb.js +19 -0
- package/dist/{cli-m1xa8be7.js → cli-f146mn5s.js} +7 -50
- package/dist/{cli-1fj0chdk.js → cli-f7dsy61y.js} +5 -3
- package/dist/{cli-f567d5c1.js → cli-ftht3zzj.js} +2782 -2589
- package/dist/{cli-a0g1gbt5.js → cli-hbv0kq6w.js} +2 -2
- package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
- package/dist/{cli-7fnkj1hj.js → cli-kpv03zhj.js} +21 -17
- package/dist/{cli-m4j6trn4.js → cli-m8yrsg97.js} +2 -2
- package/dist/cli-m9sxbkhm.js +779 -0
- package/dist/cli-mmemxxdc.js +823 -0
- package/dist/{cli-tqtvj5pt.js → cli-n8508nre.js} +1 -1
- package/dist/{cli-z96t0thj.js → cli-p1d5b073.js} +37 -2
- package/dist/{cli-gh8y8f36.js → cli-rw51bq3j.js} +13 -10
- package/dist/{cli-59ej1c07.js → cli-smby5m17.js} +7 -6
- package/dist/{cli-65vqzz3v.js → cli-tzvk9haz.js} +94 -372
- package/dist/{cli-dc9k2ba9.js → cli-w9kb3bk1.js} +1 -1
- package/dist/{cli-kpev657q.js → cli-y6b6fb76.js} +25 -3
- package/dist/{cli-123q2get.js → cli-yz3djrm0.js} +9 -9
- package/dist/{cli-wvp6bvy4.js → cli-zhjmqxzh.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{codex-adapter-k98vaw2r.js → codex-adapter-zw4dwsx7.js} +2 -2
- package/dist/{codex-hook-9pf19cks.js → codex-hook-bw8p5nh4.js} +2 -2
- package/dist/{codex-session-runner-tngtphx7.js → codex-session-runner-k981s65v.js} +2 -2
- package/dist/{commands-y8nbr3fv.js → commands-sykrxx7e.js} +4 -4
- package/dist/{db-30p4h2t1.js → db-sfbsc8wt.js} +6 -4
- package/dist/handlers-0aspcejm.js +603 -0
- package/dist/{hook-dcw4jfbj.js → hook-4nvqj2sb.js} +7 -5
- package/dist/{hook-21jexvfx.js → hook-hxccs7m5.js} +24 -49
- package/dist/{http-r4kq5g8d.js → http-y06z517d.js} +230 -94
- package/dist/{index-q3qja6ta.js → index-hssww7kj.js} +15 -14
- package/dist/{index-gay66b9w.js → index-k8znfx0z.js} +13 -12
- package/dist/{index-53zyht4z.js → index-tce1rss4.js} +371 -110
- package/dist/{index-nqvf26za.js → index-x5j894zf.js} +14 -13
- package/dist/{keepalive-xs8fa55y.js → keepalive-w733ax66.js} +6 -6
- package/dist/{lead-d9m2t4s0.js → lead-75bjqa9d.js} +29 -27
- package/dist/{maintenance-3zh82g6z.js → maintenance-w4f1zjck.js} +8 -7
- package/dist/{oauth-refresh-sweep-03snsye5.js → oauth-refresh-sweep-wdetky26.js} +5 -5
- package/dist/{onboard-0cg1925x.js → onboard-bb5net8s.js} +2 -2
- package/dist/{otel-impl-0ab95z27.js → otel-impl-0w7c14ap.js} +1 -1
- package/dist/{pi-mono-adapter-dtkz4e7j.js → pi-mono-adapter-2has4gnp.js} +12 -2
- package/dist/{pricing-refresh-e5jhzz4j.js → pricing-refresh-9nzsrgcq.js} +6 -6
- package/dist/{rbac-roles-p1mf40z3.js → rbac-roles-r3hqjp09.js} +5 -5
- package/dist/{rbac-roles-2w3cmrkc.js → rbac-roles-z1jrffbt.js} +4 -4
- package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
- package/dist/{render-v2-xhawry3k.js → render-v2-vbr89fnb.js} +6 -5
- package/dist/{seed-pricing-t223sw91.js → seed-pricing-g62hy1pk.js} +7 -5
- package/dist/{setup-zcs0rce7.js → setup-gnfqnptp.js} +2 -2
- package/dist/{worker-qnp2ka62.js → worker-kznxp4qm.js} +29 -27
- package/openapi.json +263 -12
- package/package.json +1 -1
- package/plugin/commands/start-worker.md +4 -9
- package/plugin/pi-skills/start-worker/SKILL.md +4 -9
- package/src/be/db/agents.ts +556 -0
- package/src/be/db/context-versions.ts +117 -0
- package/src/be/db/runtime.ts +406 -0
- package/src/be/db/tasks/read.ts +1167 -0
- package/src/be/db/tasks/write.ts +953 -0
- package/src/be/db.ts +584 -3429
- package/src/be/migrations/147_scheduled_tasks_parent_task.sql +7 -0
- package/src/be/migrations/148_pages_svg.sql +123 -0
- package/src/be/migrations/149_session_tokens.sql +24 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +13 -8
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-scripts/catalog/star-history-refresh.ts +101 -0
- package/src/be/seed-scripts/catalog/star-history-renderer.ts +119 -0
- package/src/be/seed-scripts/index.ts +17 -0
- package/src/be/seed-skills/index.ts +14 -0
- package/src/be/swarm-config-guard.ts +5 -2
- package/src/be/task-attachment-store.ts +70 -0
- package/src/be/users.ts +77 -0
- package/src/commands/attachments-section.ts +37 -0
- package/src/commands/claude-managed-setup.ts +45 -3
- package/src/commands/claude-md-session.ts +408 -0
- package/src/commands/context-preamble.ts +15 -3
- package/src/commands/profile-sync.ts +205 -5
- package/src/commands/provider-credentials.ts +60 -27
- package/src/commands/runner.ts +63 -58
- package/src/commands/templates.ts +25 -2
- package/src/hooks/hook.ts +26 -67
- package/src/http/agents.ts +40 -1
- package/src/http/auth.ts +13 -1
- package/src/http/fs.ts +21 -51
- package/src/http/index.ts +2 -0
- package/src/http/mcp.ts +32 -0
- package/src/http/pages-public.ts +20 -4
- package/src/http/pages.ts +11 -3
- package/src/http/poll.ts +4 -0
- package/src/http/repos.ts +3 -3
- package/src/http/sessions.ts +82 -0
- package/src/http/tasks.ts +10 -2
- package/src/http/utils.ts +79 -1
- package/src/prompts/session-templates.ts +12 -8
- package/src/providers/acp-adapter.ts +62 -8
- package/src/providers/pi-mono-adapter.ts +23 -6
- package/src/providers/pricing-sources.md +11 -0
- package/src/scheduler/scheduler.test.ts +49 -2
- package/src/scheduler/scheduler.ts +4 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +8 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +87 -46
- package/src/slack/handlers.test.ts +1 -3
- package/src/slack/handlers.ts +102 -87
- package/src/slack/inbound-files.ts +466 -0
- package/src/slack/templates.ts +14 -0
- package/src/tasks/task-terminal-effects.ts +158 -0
- package/src/tests/acp-adapter.test.ts +369 -117
- package/src/tests/budget-claim-gate.test.ts +1 -0
- package/src/tests/build-pi-skills.test.ts +5 -7
- package/src/tests/claude-managed-setup.test.ts +52 -4
- package/src/tests/claude-md-session.test.ts +603 -0
- package/src/tests/codex-skill-resolver.test.ts +19 -0
- package/src/tests/context-versioning.test.ts +107 -0
- package/src/tests/cors-allowlist.test.ts +309 -0
- package/src/tests/credential-check.test.ts +157 -14
- package/src/tests/db-agents.test.ts +91 -0
- package/src/tests/db-context-versions.test.ts +37 -0
- package/src/tests/db-extraction-contract.test.ts +45 -0
- package/src/tests/db-runtime.test.ts +70 -0
- package/src/tests/db-task-read-contract.test.ts +624 -0
- package/src/tests/db-task-write-contract.test.ts +653 -0
- package/src/tests/db-tasks-read.test.ts +183 -0
- package/src/tests/db-tasks-write.test.ts +230 -0
- package/src/tests/defer-task.test.ts +389 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/e2e-visuals-stage.test.ts +151 -0
- package/src/tests/file-lock.test.ts +144 -0
- package/src/tests/fixtures/file-lock-holder.ts +13 -0
- package/src/tests/fixtures/hold-file-lock.ts +25 -0
- package/src/tests/heartbeat-expiry.test.ts +188 -0
- package/src/tests/http-api-integration.test.ts +78 -1
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-svg-migration.test.ts +42 -0
- package/src/tests/preload.ts +4 -1
- package/src/tests/profile-sync-warning.test.ts +16 -0
- package/src/tests/profile-sync.test.ts +126 -0
- package/src/tests/prompt-template-session.test.ts +20 -4
- package/src/tests/provider-registration.test.ts +93 -0
- package/src/tests/rbac-e2e-helpers.test.ts +53 -0
- package/src/tests/rbac-e2e-helpers.ts +19 -2
- package/src/tests/reload-config.test.ts +26 -0
- package/src/tests/routing-affinity-inventory.test.ts +1 -1
- package/src/tests/runner-attachments-section.test.ts +22 -2
- package/src/tests/runner-context-preamble.test.ts +30 -0
- package/src/tests/runner-deployment-resume.test.ts +107 -0
- package/src/tests/runner-repo-autostash.test.ts +12 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/session-tokens.test.ts +279 -0
- package/src/tests/slack-file-tools-attachments.test.ts +408 -0
- package/src/tests/slack-inbound-files.test.ts +847 -0
- package/src/tests/star-history-refresh.test.ts +102 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +21 -0
- package/src/tests/swarm-repos.test.ts +20 -0
- package/src/tests/tool-annotations.test.ts +4 -3
- package/src/tests/ui-logs-parser.test.ts +176 -0
- package/src/tools/create-page.ts +4 -2
- package/src/tools/defer-task.ts +239 -0
- package/src/tools/repos/update-repo.ts +2 -2
- package/src/tools/schedules/create-schedule.ts +1 -0
- package/src/tools/schedules/list-schedules.ts +1 -0
- package/src/tools/schedules/patch-schedule.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +1 -0
- package/src/tools/slack-download-file.ts +60 -29
- package/src/tools/slack-read.ts +73 -32
- package/src/tools/store-progress.ts +16 -146
- package/src/tools/tool-config.ts +1 -0
- package/src/tools/utils.ts +21 -0
- package/src/types.ts +37 -4
- package/src/utils/acp-session-token.ts +56 -0
- package/src/utils/file-lock.ts +111 -0
- package/src/utils/heartbeat-expiry.ts +78 -0
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/task-attachment-links.ts +22 -0
- package/templates/skills/code-quality/content.md +8 -5
- package/templates/skills/download-task-attachment/content.md +2 -1
- package/{plugin/pi-skills → templates/skills}/review-offered-task/SKILL.md +5 -5
- package/templates/skills/review-offered-task/config.json +14 -0
- package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md} +3 -8
- package/templates/skills/scheduling/content.md +4 -0
- package/templates/skills/slack-interaction/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +16 -0
- package/templates/skills/work-on-task/config.json +14 -0
- package/templates/skills/work-on-task/content.md +11 -0
- package/dist/assistant-0qt42nzg.js +0 -212
- package/dist/cli-fs1kkqgr.js +0 -317
- package/dist/cli-jnf5zghe.js +0 -269
- package/dist/cli-n1q8q14d.js +0 -177
- package/dist/handlers-yfp1qn81.js +0 -592
- package/plugin/commands/work-on-task.md +0 -39
- package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
package/src/types.ts
CHANGED
|
@@ -1145,8 +1145,8 @@ export type AgentLatestModel = z.infer<typeof AgentLatestModelSchema>;
|
|
|
1145
1145
|
|
|
1146
1146
|
/**
|
|
1147
1147
|
* Worker-reported Bedrock enumeration block. Only present when the pi harness
|
|
1148
|
-
* is in Bedrock
|
|
1149
|
-
* `MODEL_OVERRIDE=amazon-bedrock/*`). Rides inside `cred_status` JSON (no new
|
|
1148
|
+
* is in a Bedrock mode (`BEDROCK_AUTH_MODE=sdk`, `BEDROCK_AUTH_MODE=bearer`,
|
|
1149
|
+
* or `MODEL_OVERRIDE=amazon-bedrock/*`). Rides inside `cred_status` JSON (no new
|
|
1150
1150
|
* DB column). `models` is the intersection of the models invocable by this
|
|
1151
1151
|
* account/region (on-demand/ACTIVE foundation models ∪ inference profiles) with
|
|
1152
1152
|
* the set the pi-ai Converse harness can actually drive — Converse-incompatible
|
|
@@ -1287,6 +1287,20 @@ export type VersionMeta = {
|
|
|
1287
1287
|
changeReason?: string | null;
|
|
1288
1288
|
};
|
|
1289
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Compare-and-set token for a profile update, per field: the content hash the
|
|
1292
|
+
* writer based its edit on (e.g. what a session materialized from the DB). A
|
|
1293
|
+
* field whose current DB hash differs is dropped: the DB moved since the writer
|
|
1294
|
+
* read it, so its copy is stale, not an edit.
|
|
1295
|
+
*/
|
|
1296
|
+
export type ProfileExpectedHashes = Partial<Record<VersionableField, string>>;
|
|
1297
|
+
|
|
1298
|
+
export type ProfileSyncConflict = {
|
|
1299
|
+
field: VersionableField;
|
|
1300
|
+
expectedHash: string;
|
|
1301
|
+
currentHash: string;
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1290
1304
|
// Channel Types
|
|
1291
1305
|
export const ChannelTypeSchema = z.enum(["public", "dm"]);
|
|
1292
1306
|
|
|
@@ -1534,6 +1548,7 @@ export const EventNameSchema = z.enum([
|
|
|
1534
1548
|
"system.error",
|
|
1535
1549
|
"system.profile_sync_rejected",
|
|
1536
1550
|
"system.profile_sync_reconciled",
|
|
1551
|
+
"system.profile_sync_conflict",
|
|
1537
1552
|
// Script catalog events
|
|
1538
1553
|
"script.global_upsert",
|
|
1539
1554
|
// Schedule events
|
|
@@ -1599,6 +1614,9 @@ export const ScheduledTaskSchema = z
|
|
|
1599
1614
|
lastRunAt: z.iso.datetime().optional(),
|
|
1600
1615
|
nextRunAt: z.iso.datetime().optional(),
|
|
1601
1616
|
createdByAgentId: z.string().optional(),
|
|
1617
|
+
// Set only by `defer-task`: the task this schedule wakes up to continue.
|
|
1618
|
+
// Passed through as the created task's `parentTaskId`.
|
|
1619
|
+
parentTaskId: z.string().optional(),
|
|
1602
1620
|
timezone: z.string().default("UTC"),
|
|
1603
1621
|
consecutiveErrors: z.number().int().min(0).default(0),
|
|
1604
1622
|
lastErrorAt: z.iso.datetime().optional(),
|
|
@@ -1692,6 +1710,21 @@ export const RepoGuidelinesSchema = z
|
|
|
1692
1710
|
|
|
1693
1711
|
export type RepoGuidelines = z.infer<typeof RepoGuidelinesSchema>;
|
|
1694
1712
|
|
|
1713
|
+
/** Upper bound per guideline list. The repository prompt section renders every
|
|
1714
|
+
* entry outside the bootstrap budget, so the cap lives at the write boundary. */
|
|
1715
|
+
export const REPO_GUIDELINES_MAX_ENTRIES = 50;
|
|
1716
|
+
|
|
1717
|
+
/** Write-side guidelines schema: same shape, bounded lists. Reads keep the
|
|
1718
|
+
* unbounded `RepoGuidelinesSchema` so an older row never fails a response. */
|
|
1719
|
+
export const RepoGuidelinesInputSchema = z
|
|
1720
|
+
.object({
|
|
1721
|
+
prChecks: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1722
|
+
mergeChecks: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1723
|
+
allowMerge: z.boolean().optional().default(false),
|
|
1724
|
+
review: z.array(z.string()).max(REPO_GUIDELINES_MAX_ENTRIES),
|
|
1725
|
+
})
|
|
1726
|
+
.openapi("RepoGuidelinesInput");
|
|
1727
|
+
|
|
1695
1728
|
export const RepoHooksSchema = z
|
|
1696
1729
|
.object({
|
|
1697
1730
|
enabled: z.boolean().default(false),
|
|
@@ -2212,10 +2245,10 @@ export type WorkflowVersion = z.infer<typeof WorkflowVersionSchema>;
|
|
|
2212
2245
|
// Pages — DB-backed lightweight artifacts (HTML or JSON spec) stored in
|
|
2213
2246
|
// SQLite and served at /p/:id. See plan: thoughts/taras/plans/2026-05-12-db-backed-pages/.
|
|
2214
2247
|
// PageContentTypeSchema + PageAuthModeSchema MUST stay in sync with the SQL
|
|
2215
|
-
// CHECK constraints in src/be/migrations/
|
|
2248
|
+
// CHECK constraints in src/be/migrations/148_pages_svg.sql.
|
|
2216
2249
|
// ---------------------------------------------------------------------------
|
|
2217
2250
|
|
|
2218
|
-
export const PageContentTypeSchema = z.enum(["text/html", "application/json"]);
|
|
2251
|
+
export const PageContentTypeSchema = z.enum(["text/html", "application/json", "image/svg+xml"]);
|
|
2219
2252
|
export type PageContentType = z.infer<typeof PageContentTypeSchema>;
|
|
2220
2253
|
|
|
2221
2254
|
export const PageAuthModeSchema = z.enum(["public", "authed", "password"]);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP helpers used by the ACP adapter to mint and revoke ephemeral session
|
|
3
|
+
* tokens via the swarm API. This keeps the provider layer free of direct
|
|
4
|
+
* DB imports while still allowing the adapter to exchange the full operator
|
|
5
|
+
* key for a short-lived aseph_ bearer before handing credentials to the
|
|
6
|
+
* ACP target process.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Wall-clock TTL for a session token: 24 hours. Long enough for any realistic
|
|
10
|
+
* ACP session; the token is actively revoked when the session finishes anyway. */
|
|
11
|
+
export const ACP_SESSION_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ask the swarm API to mint a short-lived `aseph_` bearer for the given
|
|
15
|
+
* agent + task. Throws if the request fails so callers fail closed rather
|
|
16
|
+
* than falling back to the full operator key.
|
|
17
|
+
*/
|
|
18
|
+
export async function mintAcpSessionToken(
|
|
19
|
+
apiUrl: string,
|
|
20
|
+
apiKey: string,
|
|
21
|
+
agentId: string,
|
|
22
|
+
taskId: string,
|
|
23
|
+
): Promise<{ tokenId: string; plaintext: string }> {
|
|
24
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ agentId, taskId, ttlMs: ACP_SESSION_TOKEN_TTL_MS }),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`\x1b[33m[acp]\x1b[0m Session token mint failed (${res.status})`);
|
|
34
|
+
}
|
|
35
|
+
const data = (await res.json()) as { tokenId: string; plaintext: string };
|
|
36
|
+
return { tokenId: data.tokenId, plaintext: data.plaintext };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ask the swarm API to revoke a previously-minted `aseph_` token. Best-effort:
|
|
41
|
+
* errors are logged but never propagated to the caller.
|
|
42
|
+
*/
|
|
43
|
+
export async function revokeAcpSessionToken(
|
|
44
|
+
apiUrl: string,
|
|
45
|
+
apiKey: string,
|
|
46
|
+
tokenId: string,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
try {
|
|
49
|
+
await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens/${tokenId}`, {
|
|
50
|
+
method: "DELETE",
|
|
51
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
// best-effort — token will expire on its own
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory cross-process lock held by the kernel: flock(2) on a lock file, via
|
|
3
|
+
* `bun:ffi` (Bun and Node expose no flock).
|
|
4
|
+
*
|
|
5
|
+
* Makes a multi-file state transition exclusive across processes of the same
|
|
6
|
+
* user. First use: the Claude hook rewrites `~/.claude/CLAUDE.md` together with
|
|
7
|
+
* its lineage record, and the hook and the runner read that pair — a reader
|
|
8
|
+
* landing between the two writes can misjudge the file (see
|
|
9
|
+
* `src/commands/claude-md-session.ts`).
|
|
10
|
+
*
|
|
11
|
+
* Why the kernel and not a lock file with a staleness threshold: a threshold
|
|
12
|
+
* cannot tell a slow holder (a stalled filesystem, a suspended process) from a
|
|
13
|
+
* dead one, so it eventually lets a second process into a transition that is
|
|
14
|
+
* still running. flock is released only when the holder closes its descriptor or
|
|
15
|
+
* dies, so it can never be taken from a live holder and needs no breaking.
|
|
16
|
+
*
|
|
17
|
+
* The lock file is never deleted: unlinking a locked path would let a newcomer
|
|
18
|
+
* create and lock a different inode while the old one is still held.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
22
|
+
// A file handle whose descriptor stays open while the lock is held has no Bun
|
|
23
|
+
// equivalent; neither does directory creation.
|
|
24
|
+
import { mkdir, open } from "node:fs/promises";
|
|
25
|
+
import { dirname } from "node:path";
|
|
26
|
+
|
|
27
|
+
const LOCK_EX = 2;
|
|
28
|
+
const LOCK_NB = 4;
|
|
29
|
+
|
|
30
|
+
export type Flock = (fd: number, operation: number) => number;
|
|
31
|
+
|
|
32
|
+
const LIBC_CANDIDATES =
|
|
33
|
+
process.platform === "darwin"
|
|
34
|
+
? ["libc.dylib", "/usr/lib/libSystem.B.dylib"]
|
|
35
|
+
: ["libc.so.6", "libc.so", "libc.musl-x86_64.so.1", "libc.musl-aarch64.so.1"];
|
|
36
|
+
|
|
37
|
+
let flockImpl: Flock | null | undefined;
|
|
38
|
+
|
|
39
|
+
function loadFlock(): Flock | null {
|
|
40
|
+
if (flockImpl !== undefined) return flockImpl;
|
|
41
|
+
flockImpl = null;
|
|
42
|
+
for (const name of LIBC_CANDIDATES) {
|
|
43
|
+
try {
|
|
44
|
+
const { symbols } = dlopen(name, {
|
|
45
|
+
flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
46
|
+
});
|
|
47
|
+
flockImpl = (fd, operation) => symbols.flock(fd, operation);
|
|
48
|
+
break;
|
|
49
|
+
} catch {
|
|
50
|
+
// try the next libc name
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return flockImpl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULTS = { waitMs: 15_000, pollMs: 25 };
|
|
57
|
+
|
|
58
|
+
export interface FileLockOptions {
|
|
59
|
+
/** How long to wait for a held lock before giving up (default 15 s). */
|
|
60
|
+
waitMs?: number;
|
|
61
|
+
/** Poll interval while waiting (default 25 ms). */
|
|
62
|
+
pollMs?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type FileLockResult<T> =
|
|
66
|
+
| { acquired: true; value: T }
|
|
67
|
+
/**
|
|
68
|
+
* `busy`: another live process holds it (or flock keeps refusing).
|
|
69
|
+
* `error`: the lock file could not be created or opened.
|
|
70
|
+
* `unsupported`: no flock on this platform.
|
|
71
|
+
*/
|
|
72
|
+
| { acquired: false; reason: "busy" | "error" | "unsupported"; error?: unknown };
|
|
73
|
+
|
|
74
|
+
/** Test seam: replace the flock binding (`null` = unsupported platform, `undefined` = real). */
|
|
75
|
+
export function setFlockForTests(impl: Flock | null | undefined): void {
|
|
76
|
+
flockImpl = impl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run `fn` while holding the lock. Never runs `fn` without it: callers decide
|
|
81
|
+
* what a `busy`, `error` or `unsupported` result means for them. Errors thrown
|
|
82
|
+
* by `fn` propagate (after the lock is released).
|
|
83
|
+
*/
|
|
84
|
+
export async function withFileLock<T>(
|
|
85
|
+
lockPath: string,
|
|
86
|
+
fn: () => Promise<T>,
|
|
87
|
+
options: FileLockOptions = {},
|
|
88
|
+
): Promise<FileLockResult<T>> {
|
|
89
|
+
const flock = loadFlock();
|
|
90
|
+
if (!flock) return { acquired: false, reason: "unsupported" };
|
|
91
|
+
const waitMs = options.waitMs ?? DEFAULTS.waitMs;
|
|
92
|
+
const pollMs = options.pollMs ?? DEFAULTS.pollMs;
|
|
93
|
+
|
|
94
|
+
let handle: Awaited<ReturnType<typeof open>>;
|
|
95
|
+
try {
|
|
96
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
97
|
+
handle = await open(lockPath, "a");
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return { acquired: false, reason: "error", error };
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const deadline = Date.now() + waitMs;
|
|
103
|
+
while (flock(handle.fd, LOCK_EX | LOCK_NB) !== 0) {
|
|
104
|
+
if (Date.now() >= deadline) return { acquired: false, reason: "busy" };
|
|
105
|
+
await Bun.sleep(pollMs);
|
|
106
|
+
}
|
|
107
|
+
return { acquired: true, value: await fn() };
|
|
108
|
+
} finally {
|
|
109
|
+
await handle.close(); // closing the descriptor releases the lock
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { scrubSecrets } from "./secret-scrubber";
|
|
2
|
+
|
|
3
|
+
const EXPIRY = /delete\s+(on\s+)?\d{2}-\d{2}/i;
|
|
4
|
+
|
|
5
|
+
function trackedLines(markdown: string): string[] {
|
|
6
|
+
const marker = "## Tracked Items";
|
|
7
|
+
const start = markdown.lastIndexOf(marker);
|
|
8
|
+
if (start < 0) return [];
|
|
9
|
+
const lineEnd = markdown.indexOf("\n", start);
|
|
10
|
+
if (lineEnd < 0) return [];
|
|
11
|
+
// Stop at the next peer/parent section; subheadings may group tracked items.
|
|
12
|
+
const section = markdown.slice(lineEnd + 1).split(/^#{1,2}\s/m)[0] ?? "";
|
|
13
|
+
return section
|
|
14
|
+
.split(/\r?\n/)
|
|
15
|
+
.map((line) => line.trim().replace(/^(?:[-*+]\s+|\d+[.)]\s+)(?:\[[ xX]\]\s*)?/, ""))
|
|
16
|
+
.filter((line) => line && !line.startsWith("#") && !/^[-*_]{3,}$/.test(line));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function title(line: string): string | undefined {
|
|
20
|
+
return /^\*\*([^*]+)\*\*/.exec(line)?.[1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Deleting text anywhere in a legacy line is cleanup, not a new item.
|
|
24
|
+
function isShortening(old: string, next: string): boolean {
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
for (const character of next) {
|
|
27
|
+
const index = old.indexOf(character, cursor);
|
|
28
|
+
if (index < 0) return false;
|
|
29
|
+
cursor = index + character.length;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class HeartbeatExpiryError extends Error {
|
|
35
|
+
constructor(line: string) {
|
|
36
|
+
super(
|
|
37
|
+
scrubSecrets(
|
|
38
|
+
`Update rejected for heartbeatMd: new tracked item requires DELETE <MM-DD> (or DELETE on <MM-DD>): ${line}`,
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
this.name = "HeartbeatExpiryError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Grandfather existing lines, but never let one old item cover two new ones. */
|
|
46
|
+
export function validateHeartbeatExpiry(currentValue: string, nextValue: string): void {
|
|
47
|
+
const previous = trackedLines(currentValue);
|
|
48
|
+
const incoming = trackedLines(nextValue);
|
|
49
|
+
// Consume exact matches first so an unchanged item cannot sponsor a new item.
|
|
50
|
+
const changed = incoming.filter((line) => {
|
|
51
|
+
const index = previous.indexOf(line);
|
|
52
|
+
if (index < 0) return true;
|
|
53
|
+
previous.splice(index, 1);
|
|
54
|
+
return false;
|
|
55
|
+
});
|
|
56
|
+
// Dated/pinned replacements also consume their old item before undated edits.
|
|
57
|
+
// Otherwise dating one old item could accidentally authorize an undated copy.
|
|
58
|
+
for (const line of changed) {
|
|
59
|
+
if (!line.startsWith("📌") && !EXPIRY.test(line)) continue;
|
|
60
|
+
const itemTitle = title(line.replace(/^📌\s*/, ""));
|
|
61
|
+
const index = previous.findIndex(
|
|
62
|
+
(old) => line.includes(old) || (itemTitle !== undefined && itemTitle === title(old)),
|
|
63
|
+
);
|
|
64
|
+
if (index >= 0) previous.splice(index, 1);
|
|
65
|
+
}
|
|
66
|
+
for (const line of changed) {
|
|
67
|
+
if (line.startsWith("📌") || EXPIRY.test(line)) continue;
|
|
68
|
+
const itemTitle = title(line);
|
|
69
|
+
const index = previous.findIndex(
|
|
70
|
+
(old) =>
|
|
71
|
+
!old.startsWith("📌") &&
|
|
72
|
+
!EXPIRY.test(old) &&
|
|
73
|
+
(isShortening(old, line) || (itemTitle !== undefined && itemTitle === title(old))),
|
|
74
|
+
);
|
|
75
|
+
if (index < 0) throw new HeartbeatExpiryError(line);
|
|
76
|
+
previous.splice(index, 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -5,6 +5,8 @@ import type { User } from "../types";
|
|
|
5
5
|
export type HttpRequestAuth = (
|
|
6
6
|
| { kind: "operator"; fingerprint: string }
|
|
7
7
|
| { kind: "user"; userId: string; user: User }
|
|
8
|
+
/** Ephemeral session token minted by the ACP adapter per session (aseph_ prefix). */
|
|
9
|
+
| { kind: "agent"; agentId: string; taskId: string }
|
|
8
10
|
) & {
|
|
9
11
|
/** Verified page context for agent-scoped operations. This does not identify the viewer. */
|
|
10
12
|
page?: { id: string; executionAgentId: string };
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import type { TaskAttachment } from "../types";
|
|
2
2
|
import { buildAgentFsLiveUrl, getAppUrl } from "./constants";
|
|
3
3
|
|
|
4
|
+
function shellQuote(value: string): string {
|
|
5
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A ready-to-run command that downloads a task attachment's bytes from inside a
|
|
10
|
+
* worker container through the provider-agnostic raw route — MCP_BASE_URL, the
|
|
11
|
+
* API key and AGENT_ID are already in every worker's env. Each attachment gets
|
|
12
|
+
* its own directory, so two attachments with the same name (every pasted
|
|
13
|
+
* screenshot is "image.png") never overwrite each other; the path is quoted
|
|
14
|
+
* because attachment names come from users.
|
|
15
|
+
*/
|
|
16
|
+
export function taskAttachmentFetchCommand(
|
|
17
|
+
taskId: string,
|
|
18
|
+
attachmentId: string,
|
|
19
|
+
name: string,
|
|
20
|
+
): string {
|
|
21
|
+
const url = `$MCP_BASE_URL/api/fs/tasks/${taskId}/files/${attachmentId}/raw`;
|
|
22
|
+
const outPath = shellQuote(`/tmp/attachments/${attachmentId}/${name.replace(/\//g, "_")}`);
|
|
23
|
+
return `curl -s -H "Authorization: Bearer \${AGENT_SWARM_API_KEY:-$API_KEY}" -H "X-Agent-ID: $AGENT_ID" "${url}" --create-dirs -o ${outPath}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
export function taskAttachmentDisplayUrl(attachment: TaskAttachment): string {
|
|
5
27
|
if (attachment.kind === "url") return attachment.url ?? "";
|
|
6
28
|
if (attachment.kind === "page") {
|
|
@@ -38,11 +38,14 @@ The Repository Guidelines carry `allowMerge` and `mergeChecks`.
|
|
|
38
38
|
|
|
39
39
|
## Review a PR
|
|
40
40
|
|
|
41
|
-
1. CI status first. Failing CI is a REQUEST_CHANGES. Name the failing checks in the review.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
45
|
-
5.
|
|
41
|
+
1. CI status first, including the fork checks below before treating CI as evidence. Failing CI is a REQUEST_CHANGES. Name the failing checks in the review.
|
|
42
|
+
2. Detect a GitHub fork PR with `gh pr view <number> --json isCrossRepository --jq .isCrossRepository`.
|
|
43
|
+
3. For a GitHub fork PR, inspect held runs with `SHA=$(gh pr view <number> --json headRefOid --jq .headRefOid); gh api --paginate "repos/<owner>/<repo>/actions/runs?head_sha=$SHA" --jq '.workflow_runs[] | "\(.conclusion // .status)|\(.name)"'`; `gh pr checks` and `statusCheckRollup` can omit them.
|
|
44
|
+
4. Treat any `action_required` run or no substantive run executed on that SHA as a CI blocker. Keep run handling read-only: REQUEST_CHANGES naming the held or unexecuted run, and report the blocker so an explicitly authorized maintainer or the application-controlled GitHub integration can approve the run after validating the workflow and its trust boundary. Do not APPROVE on omitted runs.
|
|
45
|
+
5. Tests second. A code change without new or updated tests is a REQUEST_CHANGES. Name the tests you expect. Exceptions: documentation-only, configuration-only, and dependency-bump PRs.
|
|
46
|
+
6. Apply the "Review Guidance" entries from the Repository Guidelines.
|
|
47
|
+
7. Read the diff for security (injection, secrets in code), logic (null handling, off-by-one, edge cases), performance (N+1, leaks), and code shape (naming, duplication, error handling). Run the test suite and the type check locally when you can.
|
|
48
|
+
8. Post the review with the verdict first. One finding per comment, with file and line, and what to change.
|
|
46
49
|
|
|
47
50
|
## GitHub review-reply provenance
|
|
48
51
|
|
|
@@ -12,11 +12,12 @@ discovery.
|
|
|
12
12
|
curl "$MCP_BASE_URL/api/fs/tasks/$AGENT_SWARM_TASK_ID/files/<attachmentId>/raw" \
|
|
13
13
|
-H "X-Agent-ID: $AGENT_ID" \
|
|
14
14
|
-H "Authorization: Bearer ${AGENT_SWARM_API_KEY:-$API_KEY}" \
|
|
15
|
-
-o /tmp/<name>
|
|
15
|
+
--create-dirs -o "/tmp/attachments/<attachmentId>/<name>"
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
- `$MCP_BASE_URL`, `$AGENT_ID`, `$AGENT_SWARM_TASK_ID` (or `$AGENT_SWARM_AGENT_ID`/`TASK_FILE`-derived taskId), and `$AGENT_SWARM_API_KEY`/`$API_KEY` are already present in every worker container's env — no new plumbing needed.
|
|
19
19
|
- `<attachmentId>` and `<name>`/`mimeType` come from the task's attachment list (see below).
|
|
20
|
+
- One directory per attachment: two attachments can share a name (every pasted screenshot is `image.png`), and `/tmp/<name>` would let one overwrite the other.
|
|
20
21
|
- The route resolves the active file-storage provider (`local-fs` in dev, `agent-fs` in prod) server-side and streams the raw bytes back with the correct `Content-Type` — you get one call no matter which provider is behind it.
|
|
21
22
|
|
|
22
23
|
## Finding the attachment ID
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: review-offered-task
|
|
3
|
-
description:
|
|
3
|
+
description: "Turn prompt for an offered swarm task: read the offer, accept with task-action and start work-on-task, or reject with a reason."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Review Offered Task
|
|
@@ -17,7 +17,7 @@ You have been offered a task. Your job is to review it and decide whether to acc
|
|
|
17
17
|
- Is the task description clear enough to proceed?
|
|
18
18
|
|
|
19
19
|
3. **Make a decision**:
|
|
20
|
-
- **Accept**: If you can complete this task, call `task-action` with `action: "accept"` and `taskId: "<taskId>"`. Then immediately use
|
|
20
|
+
- **Accept**: If you can complete this task, call `task-action` with `action: "accept"` and `taskId: "<taskId>"`. Then immediately use the `work-on-task` skill with the taskId to start working on it.
|
|
21
21
|
- **Reject**: If you cannot complete this task, call `task-action` with `action: "reject"`, `taskId: "<taskId>"`, and provide a `reason` explaining why you're rejecting it (e.g., "Task requires Python expertise which I don't have", "Task description is too vague").
|
|
22
22
|
|
|
23
23
|
## Example Accept Flow
|
|
@@ -26,7 +26,7 @@ You have been offered a task. Your job is to review it and decide whether to acc
|
|
|
26
26
|
1. get-task-details taskId="abc-123"
|
|
27
27
|
2. [Review the task details]
|
|
28
28
|
3. task-action action="accept" taskId="abc-123"
|
|
29
|
-
4. /
|
|
29
|
+
4. /work-on-task abc-123
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Example Reject Flow
|
|
@@ -35,11 +35,11 @@ You have been offered a task. Your job is to review it and decide whether to acc
|
|
|
35
35
|
1. get-task-details taskId="abc-123"
|
|
36
36
|
2. [Review the task details]
|
|
37
37
|
3. task-action action="reject" taskId="abc-123" reason="Task requires access to production database which I don't have"
|
|
38
|
-
4.
|
|
38
|
+
4. Stop
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
## Important Notes
|
|
42
42
|
|
|
43
43
|
- Always provide a clear reason when rejecting a task - this helps the lead agent reassign it appropriately
|
|
44
|
-
- If you accept, you must immediately start working on the task using
|
|
44
|
+
- If you accept, you must immediately start working on the task using the `work-on-task` skill
|
|
45
45
|
- If you reject, the task returns to the unassigned pool for reassignment
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "skill",
|
|
3
|
+
"name": "review-offered-task",
|
|
4
|
+
"displayName": "Review Offered Task",
|
|
5
|
+
"slug": "review-offered-task",
|
|
6
|
+
"title": "Review Offered Task",
|
|
7
|
+
"description": "Turn prompt for an offered swarm task: read the offer, accept with task-action and start work-on-task, or reject with a reason.",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"category": "skills",
|
|
10
|
+
"placeholders": [],
|
|
11
|
+
"runAllSeedersCandidate": true,
|
|
12
|
+
"systemDefault": true,
|
|
13
|
+
"tags": ["tasks", "turn-prompt", "worker"]
|
|
14
|
+
}
|
package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md}
RENAMED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Review a task that has been offered to you and decide whether to accept or reject it
|
|
3
|
-
argument-hint: [taskId]
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Review Offered Task
|
|
7
2
|
|
|
8
3
|
You have been offered a task. Your job is to review it and decide whether to accept or reject it based on your capabilities and current workload.
|
|
@@ -17,7 +12,7 @@ You have been offered a task. Your job is to review it and decide whether to acc
|
|
|
17
12
|
- Is the task description clear enough to proceed?
|
|
18
13
|
|
|
19
14
|
3. **Make a decision**:
|
|
20
|
-
- **Accept**: If you can complete this task, call `task-action` with `action: "accept"` and `taskId: "<taskId>"`. Then immediately use
|
|
15
|
+
- **Accept**: If you can complete this task, call `task-action` with `action: "accept"` and `taskId: "<taskId>"`. Then immediately use the `work-on-task` skill with the taskId to start working on it.
|
|
21
16
|
- **Reject**: If you cannot complete this task, call `task-action` with `action: "reject"`, `taskId: "<taskId>"`, and provide a `reason` explaining why you're rejecting it (e.g., "Task requires Python expertise which I don't have", "Task description is too vague").
|
|
22
17
|
|
|
23
18
|
## Example Accept Flow
|
|
@@ -35,11 +30,11 @@ You have been offered a task. Your job is to review it and decide whether to acc
|
|
|
35
30
|
1. get-task-details taskId="abc-123"
|
|
36
31
|
2. [Review the task details]
|
|
37
32
|
3. task-action action="reject" taskId="abc-123" reason="Task requires access to production database which I don't have"
|
|
38
|
-
4.
|
|
33
|
+
4. Stop
|
|
39
34
|
```
|
|
40
35
|
|
|
41
36
|
## Important Notes
|
|
42
37
|
|
|
43
38
|
- Always provide a clear reason when rejecting a task - this helps the lead agent reassign it appropriately
|
|
44
|
-
- If you accept, you must immediately start working on the task using
|
|
39
|
+
- If you accept, you must immediately start working on the task using the `work-on-task` skill
|
|
45
40
|
- If you reject, the task returns to the unassigned pool for reassignment
|
|
@@ -41,6 +41,10 @@ The template is the whole task the agent receives. It must state:
|
|
|
41
41
|
|
|
42
42
|
Tasks created by a schedule are automatic tasks. Their completed output is not stored as memory unless the agent calls `store-progress` with `persistMemory: true`.
|
|
43
43
|
|
|
44
|
+
## Check back later
|
|
45
|
+
|
|
46
|
+
A task whose answer needs time (a build, a deploy, a reply) calls `defer-task` with `delayMs` or `runAt`, a `summary` of what you did so far, and a `note` that says what is pending. The tool completes the task and creates the one-off schedule for you. The task reaches its final state (`completed`) on this call, and the `summary` becomes its output. The wake-up task carries the deferred task as its parent, so you receive that task's context. Add `checks` to list what to verify on wake-up. Do not hand-roll this with `create-schedule`.
|
|
47
|
+
|
|
44
48
|
## Secrets
|
|
45
49
|
|
|
46
50
|
A `taskTemplate` and `scriptArgs` are stored as plain text and replayed on every run. They must not contain a token, password, or key. Call the external API from a script through a registered connection or a credential binding instead. See the `swarm-scripts` skill, section Secrets.
|
|
@@ -30,12 +30,12 @@ The task carries `slackChannelId` and `slackThreadTs` in its metadata. A follow-
|
|
|
30
30
|
| `slack-read` | read a thread by `taskId` or `inboxMessageId`, or a channel by `channelId` (lead) |
|
|
31
31
|
| `slack-list-channels` | channels the bot is a member of |
|
|
32
32
|
| `slack-upload-file` | upload a file to a thread or channel, up to 1 GB |
|
|
33
|
-
| `slack-download-file` |
|
|
33
|
+
| `slack-download-file` | attach a Slack file (by ID or URL) to your task; run the `fetchCommand` it returns to get the bytes |
|
|
34
34
|
| `slack-update` | edit your own message |
|
|
35
35
|
| `slack-delete` | delete your own message (lead) |
|
|
36
36
|
| `slack-create-channel`, `slack-invite-to-channel`, `slack-archive-channel` | channel lifecycle (lead) |
|
|
37
37
|
|
|
38
|
-
Files attached to your task already come with download commands in the task message. Use those before `slack-download-file`.
|
|
38
|
+
Files attached to your task already come with download commands in the task message. Use those before `slack-download-file`. `slack-read` on your task also attaches the files in the thread and gives each one a `fetchCommand`. Don't look for Slack files under `/app/shared` or `/workspace/shared/downloads`: those paths are the API server's disk.
|
|
39
39
|
|
|
40
40
|
## Unknown user
|
|
41
41
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: work-on-task
|
|
3
|
+
description: "Turn prompt for an assigned swarm task: find the task, gather context when the message lacks it, use the skill the task names, finish with one of the four endings."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Working on a task
|
|
7
|
+
|
|
8
|
+
The taskId follows the command. Without one, call `get-tasks` with `mineOnly: true` and take your `pending` or `in_progress` task. If there is none, say so and stop.
|
|
9
|
+
|
|
10
|
+
This message carries the task text, its attachments, its output format, and memories from past sessions. If it does not (you invoked this command yourself, or the context was compacted), run the `task-context-gathering` script with the taskId.
|
|
11
|
+
|
|
12
|
+
When the task names a skill (`researching`, `planning`, `implementing`), use it. Otherwise work directly.
|
|
13
|
+
|
|
14
|
+
Finish the task with one of the four endings in your operating contract: `completed`, `defer-task`, `request-human-input`, or `failed`. Then stop.
|
|
15
|
+
|
|
16
|
+
If the user interrupts, follow their instructions. To resume, call `/work-on-task <taskId>` again.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "skill",
|
|
3
|
+
"name": "work-on-task",
|
|
4
|
+
"displayName": "Work On Task",
|
|
5
|
+
"slug": "work-on-task",
|
|
6
|
+
"title": "Work On Task",
|
|
7
|
+
"description": "Turn prompt for an assigned swarm task: find the task, gather context when the message lacks it, use the skill the task names, finish with one of the four endings.",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"category": "skills",
|
|
10
|
+
"placeholders": [],
|
|
11
|
+
"runAllSeedersCandidate": true,
|
|
12
|
+
"systemDefault": true,
|
|
13
|
+
"tags": ["tasks", "turn-prompt", "worker"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Working on a task
|
|
2
|
+
|
|
3
|
+
The taskId follows the command. Without one, call `get-tasks` with `mineOnly: true` and take your `pending` or `in_progress` task. If there is none, say so and stop.
|
|
4
|
+
|
|
5
|
+
This message carries the task text, its attachments, its output format, and memories from past sessions. If it does not (you invoked this command yourself, or the context was compacted), run the `task-context-gathering` script with the taskId.
|
|
6
|
+
|
|
7
|
+
When the task names a skill (`researching`, `planning`, `implementing`), use it. Otherwise work directly.
|
|
8
|
+
|
|
9
|
+
Finish the task with one of the four endings in your operating contract: `completed`, `defer-task`, `request-human-input`, or `failed`. Then stop.
|
|
10
|
+
|
|
11
|
+
If the user interrupts, follow their instructions. To resume, call `/work-on-task <taskId>` again.
|