@desplega.ai/agent-swarm 1.131.0 → 1.133.1
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 -2
- package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
- package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
- package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
- package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
- package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
- package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
- package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
- package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
- package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
- package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
- package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
- package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
- package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
- package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
- package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
- package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
- package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
- package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
- package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
- package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
- package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
- package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
- package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
- package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
- package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
- package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
- package/dist/cli-et1bbwqf.js +13 -0
- package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
- package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
- package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
- package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
- package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
- package/dist/cli-hkwfe3qj.js +530 -0
- package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
- package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
- package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
- package/dist/cli-pzq5rjym.js +15 -0
- package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
- package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
- package/dist/cli-qys3rhhz.js +27 -0
- package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
- package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
- package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
- package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
- package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
- package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
- package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
- package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
- package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
- package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
- package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
- package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
- package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
- package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
- package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
- package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
- package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
- package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
- package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
- package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
- package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
- package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
- package/dist/index-0gr8vd6k.js +39 -0
- package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
- package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
- package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
- package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
- package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
- package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
- package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
- package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
- package/dist/mistral-conversations-00zrp7g4.js +717 -0
- package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
- package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
- package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
- package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
- package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
- package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
- package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
- package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
- package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
- package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
- package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
- package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
- package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
- package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
- package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
- package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
- package/openapi.json +542 -16
- package/package.json +6 -6
- package/src/be/budget-refusal-notify.ts +14 -7
- package/src/be/db-queries/task-artifacts.ts +182 -0
- package/src/be/db.ts +783 -169
- package/src/be/events.ts +5 -0
- package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
- package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
- package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
- package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
- package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
- package/src/be/migrations/136_task_requester_provenance.sql +37 -0
- package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
- package/src/be/multi-runtime.ts +430 -0
- package/src/be/scripts/db.ts +43 -1
- package/src/be/scripts/extract-schema.ts +3 -1
- package/src/be/scripts/retention.ts +164 -0
- package/src/be/scripts/run-saved.ts +16 -1
- package/src/be/scripts/typecheck.ts +3 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -1
- package/src/be/swarm-config-guard.ts +18 -0
- package/src/commands/credential-wait.ts +54 -0
- package/src/commands/onboard/steps/core-credentials.tsx +1 -1
- package/src/commands/onboard/steps/custom-templates.tsx +1 -1
- package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
- package/src/commands/onboard/steps/health-check.tsx +1 -1
- package/src/commands/onboard/steps/start.tsx +1 -1
- package/src/commands/profile-sync-audit.ts +56 -0
- package/src/commands/profile-sync.ts +287 -15
- package/src/commands/provider-credentials.ts +46 -19
- package/src/commands/runner.ts +196 -33
- package/src/commands/templates.ts +22 -0
- package/src/github/handlers.ts +55 -9
- package/src/heartbeat/heartbeat.ts +71 -12
- package/src/hooks/hook.ts +49 -33
- package/src/http/active-sessions.ts +11 -1
- package/src/http/agents.ts +261 -30
- package/src/http/all-routes.ts +1 -0
- package/src/http/config.ts +13 -2
- package/src/http/core.ts +102 -4
- package/src/http/db-query-bounded.ts +313 -0
- package/src/http/db-query-shared.ts +249 -0
- package/src/http/db-query.ts +54 -42
- package/src/http/events.ts +2 -0
- package/src/http/index.ts +27 -2
- package/src/http/mcp-bridge.ts +37 -3
- package/src/http/mcp.ts +12 -0
- package/src/http/memory.ts +13 -2
- package/src/http/openapi.ts +1 -0
- package/src/http/poll.ts +70 -27
- package/src/http/route-def.ts +43 -9
- package/src/http/schedules.ts +2 -2
- package/src/http/scripts.ts +31 -4
- package/src/http/session-data.ts +44 -0
- package/src/http/tasks.ts +2 -2
- package/src/http/users.ts +74 -52
- package/src/http/webhooks.ts +5 -2
- package/src/http/workflows.ts +1 -4
- package/src/http/x.ts +14 -0
- package/src/integrations/kapso/inbound.ts +1 -1
- package/src/jira/outbound.ts +39 -8
- package/src/jira/webhook-lifecycle.ts +13 -2
- package/src/linear/outbound.ts +43 -10
- package/src/oauth/keepalive.ts +13 -2
- package/src/prompts/base-prompt.ts +1 -1
- package/src/prompts/session-templates.ts +45 -7
- package/src/providers/claude-adapter.ts +6 -0
- package/src/providers/claude-managed-models.ts +34 -10
- package/src/providers/codex-adapter.ts +6 -0
- package/src/providers/opencode-adapter.ts +6 -0
- package/src/providers/pi-mono-adapter.ts +8 -0
- package/src/providers/pricing-sources.md +24 -0
- package/src/queue-stall-alarm.ts +216 -0
- package/src/scheduler/scheduler.ts +7 -0
- package/src/script-workflows/executor.ts +2 -1
- package/src/scripts-runtime/executors/native.ts +2 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/loader.ts +5 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/tests/budget-claim-gate.test.ts +20 -0
- package/src/tests/claude-adapter.test.ts +32 -0
- package/src/tests/claude-managed-adapter.test.ts +38 -7
- package/src/tests/codex-adapter.test.ts +30 -0
- package/src/tests/create-task-validation.test.ts +128 -0
- package/src/tests/credential-wait.test.ts +67 -1
- package/src/tests/db-query.test.ts +711 -2
- package/src/tests/http-api-integration.test.ts +81 -0
- package/src/tests/http-users.test.ts +166 -1
- package/src/tests/identity-field-budget.test.ts +176 -0
- package/src/tests/mcp-tools-user.test.ts +34 -0
- package/src/tests/mcp-user-route.test.ts +17 -0
- package/src/tests/metrics-http.test.ts +44 -0
- package/src/tests/multi-runtime-registration.test.ts +2703 -0
- package/src/tests/openapi-response-contract.test.ts +24 -0
- package/src/tests/openapi-route-headers.test.ts +83 -0
- package/src/tests/opencode-adapter.test.ts +40 -0
- package/src/tests/outbound-listener-rejection.test.ts +138 -0
- package/src/tests/pi-mono-adapter.test.ts +98 -0
- package/src/tests/profile-sync-warning.test.ts +277 -0
- package/src/tests/profile-sync.test.ts +232 -6
- package/src/tests/prompt-template-session.test.ts +32 -3
- package/src/tests/queue-stall-alarm.test.ts +226 -0
- package/src/tests/requester-comms.test.ts +32 -0
- package/src/tests/runner-requester-profile.test.ts +38 -0
- package/src/tests/sandboxed-process.test.ts +45 -0
- package/src/tests/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +16 -3
- package/src/tests/scripts-external-api.test.ts +8 -3
- package/src/tests/scripts-http.test.ts +12 -0
- package/src/tests/scripts-retention.test.ts +384 -0
- package/src/tests/scripts-runtime-identity.test.ts +375 -0
- package/src/tests/scripts-runtime.test.ts +4 -3
- package/src/tests/sdk-allowlist.test.ts +47 -0
- package/src/tests/send-task-requested-by.test.ts +9 -0
- package/src/tests/session-costs.test.ts +506 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
- package/src/tests/steering-core.test.ts +28 -0
- package/src/tests/task-artifacts.test.ts +386 -0
- package/src/tests/task-attachments-schema.test.ts +17 -1
- package/src/tests/task-completion-idempotency.test.ts +57 -0
- package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
- package/src/tests/update-profile-api.test.ts +67 -39
- package/src/tests/update-profile-auth.test.ts +48 -4
- package/src/tests/user-attribution-migration.test.ts +54 -0
- package/src/tests/user-identity.test.ts +72 -0
- package/src/tests/workflow-async-v2.test.ts +39 -1
- package/src/tests/workflow-http-v2.test.ts +3 -2
- package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
- package/src/tests/workflow-retry-v2.test.ts +45 -1
- package/src/tests/workflow-swarm-script.test.ts +14 -5
- package/src/tools/db-query.ts +18 -8
- package/src/tools/get-task-details.ts +24 -3
- package/src/tools/manage-user.ts +36 -3
- package/src/tools/poll-task.ts +93 -14
- package/src/tools/script-common.ts +5 -0
- package/src/tools/send-task.ts +1 -4
- package/src/tools/store-progress.ts +13 -2
- package/src/tools/swarm-config/delete-config.ts +9 -0
- package/src/tools/swarm-config/set-config.ts +3 -2
- package/src/tools/task-action.ts +30 -0
- package/src/tools/task-tool-ctx.ts +8 -1
- package/src/tools/update-profile.ts +16 -21
- package/src/tools/utils.ts +5 -0
- package/src/tools/workflows/trigger-workflow.ts +7 -1
- package/src/types.ts +165 -1
- package/src/utils/github-pull-request.ts +76 -0
- package/src/utils/identity-field-budget.ts +73 -0
- package/src/utils/multi-runtime.ts +20 -0
- package/src/utils/requester-comms.ts +24 -0
- package/src/utils/sandboxed-process.ts +40 -14
- package/src/workflows/engine.ts +22 -2
- package/src/workflows/executors/swarm-script.ts +24 -1
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- package/templates/schedules/daily-compounding-reflection/content.md +12 -3
- package/templates/skills/db-query-guidance/config.json +14 -0
- package/templates/skills/db-query-guidance/content.md +57 -0
- package/dist/cli-3te4jdkm.js +0 -225
- package/dist/cli-bmze986d.js +0 -7
- package/dist/cli-kh3ba2zf.js +0 -607
- package/dist/index-hp1kdbjj.js +0 -36
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getScriptMcpConnectionDescriptors,
|
|
6
6
|
} from "../script-connections";
|
|
7
7
|
import { buildScriptCredentialBindingsWithFailures } from "../script-credential-broker";
|
|
8
|
+
import { restoreScratchScriptLastUsedIfUnchanged, touchScratchScriptLastUsed } from "./db";
|
|
8
9
|
|
|
9
10
|
export function getSavedScriptOwnerAgentId(script: ScriptRecord): string | null {
|
|
10
11
|
return script.scopeId ?? script.createdByAgentId;
|
|
@@ -16,10 +17,15 @@ export async function runSavedScriptAsAgent(args: {
|
|
|
16
17
|
input: unknown;
|
|
17
18
|
agentId: string;
|
|
18
19
|
}) {
|
|
20
|
+
// Touch before executing (not just after) so a scratch script that's already
|
|
21
|
+
// stale when a run starts can't be reaped by the retention sweep while the
|
|
22
|
+
// run — which may take up to the runtime's wall-clock ceiling — is in flight.
|
|
23
|
+
const runStartTouch = args.script.isScratch ? touchScratchScriptLastUsed(args.script.id) : null;
|
|
24
|
+
|
|
19
25
|
const credentials = await buildScriptCredentialBindingsWithFailures({
|
|
20
26
|
agentId: args.agentId,
|
|
21
27
|
});
|
|
22
|
-
|
|
28
|
+
const output = await runScript({
|
|
23
29
|
source: args.script.source,
|
|
24
30
|
args: args.input,
|
|
25
31
|
fsMode: args.script.fsMode,
|
|
@@ -29,4 +35,13 @@ export async function runSavedScriptAsAgent(args: {
|
|
|
29
35
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: args.agentId }),
|
|
30
36
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: args.agentId }),
|
|
31
37
|
});
|
|
38
|
+
if (output.exitCode === 0 && !output.error && !output.runtimeError) {
|
|
39
|
+
touchScratchScriptLastUsed(args.script.id);
|
|
40
|
+
} else if (runStartTouch) {
|
|
41
|
+
// The run-start touch didn't pan out — restore the pre-run timestamp so a
|
|
42
|
+
// failed invocation doesn't buy the script another retention window, as
|
|
43
|
+
// long as no concurrent run has touched it since.
|
|
44
|
+
restoreScratchScriptLastUsedIfUnchanged(args.script.id, args.script.updatedAt, runStartTouch);
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
32
47
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import ts from "typescript";
|
|
2
3
|
import { getScriptAppTypes } from "@/apps/script-types";
|
|
3
4
|
import { getScriptApiTypes, getScriptMcpTypes } from "@/be/script-connections";
|
|
@@ -127,7 +128,7 @@ export interface SwarmSdk {
|
|
|
127
128
|
// --- repos ---
|
|
128
129
|
repo_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
129
130
|
// --- schedules ---
|
|
130
|
-
schedule_list(args?: { enabled?: boolean; name?: string; scheduleType?: "recurring" | "one_time"; targetType?: "agent-task" | "workflow" | "script"; workflowId?: string; scriptName?: string; hideCompleted?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "failed" | "succeeded" }): Promise<unknown>;
|
|
131
|
+
schedule_list(args?: { enabled?: boolean; name?: string; scheduleType?: "recurring" | "one_time"; targetType?: "agent-task" | "workflow" | "script"; workflowId?: string; scriptName?: string; hideCompleted?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "failed" | "succeeded"; includeFull?: boolean }): Promise<unknown>;
|
|
131
132
|
// --- scripts ---
|
|
132
133
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
133
134
|
script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; idempotencyKey?: string }): Promise<unknown>;
|
|
@@ -799,7 +800,7 @@ const RUNTIME_GLOBALS_FILE = "/virtual/runtime-globals.d.ts";
|
|
|
799
800
|
function scriptTypesBase(): string {
|
|
800
801
|
const dir = process.env.SCRIPT_TYPES_DIR;
|
|
801
802
|
if (dir) return `${dir}/index.ts`;
|
|
802
|
-
return new URL("../../index.ts", import.meta.url)
|
|
803
|
+
return fileURLToPath(new URL("../../index.ts", import.meta.url));
|
|
803
804
|
}
|
|
804
805
|
|
|
805
806
|
function createCompilerHost(
|
|
@@ -58,6 +58,12 @@ import composioGoogleDocsConfig from "../../../templates/skills/composio-google-
|
|
|
58
58
|
import composioGoogleDocsContent from "../../../templates/skills/composio-google-docs/content.md" with {
|
|
59
59
|
type: "text",
|
|
60
60
|
};
|
|
61
|
+
import dbQueryGuidanceConfig from "../../../templates/skills/db-query-guidance/config.json" with {
|
|
62
|
+
type: "text",
|
|
63
|
+
};
|
|
64
|
+
import dbQueryGuidanceContent from "../../../templates/skills/db-query-guidance/content.md" with {
|
|
65
|
+
type: "text",
|
|
66
|
+
};
|
|
61
67
|
import delegateWorkConfig from "../../../templates/skills/delegate-work/config.json" with {
|
|
62
68
|
type: "text",
|
|
63
69
|
};
|
|
@@ -270,6 +276,7 @@ const BUILT_IN_SKILL_SOURCES = [
|
|
|
270
276
|
{ config: composioGmailConfig, body: composioGmailContent },
|
|
271
277
|
{ config: composioGoogleCalendarConfig, body: composioGoogleCalendarContent },
|
|
272
278
|
{ config: composioGoogleDocsConfig, body: composioGoogleDocsContent },
|
|
279
|
+
{ config: dbQueryGuidanceConfig, body: dbQueryGuidanceContent },
|
|
273
280
|
{ config: delegateWorkConfig, body: delegateWorkContent },
|
|
274
281
|
{ config: designDocsConfig, body: designDocsContent },
|
|
275
282
|
{ config: downloadTaskAttachmentConfig, body: downloadTaskAttachmentContent },
|
package/src/be/steering.ts
CHANGED
|
@@ -76,7 +76,7 @@ export function promoteSteeringToTask(task: AgentTask, message: SteeringMessage)
|
|
|
76
76
|
source: message.source === "script" ? "api" : message.source,
|
|
77
77
|
taskType: "follow-up",
|
|
78
78
|
parentTaskId: task.id,
|
|
79
|
-
requestedByUserId: message.createdByUserId
|
|
79
|
+
requestedByUserId: message.createdByUserId,
|
|
80
80
|
bypassTrackerContextDedup: true,
|
|
81
81
|
});
|
|
82
82
|
}
|
|
@@ -126,7 +126,19 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
126
126
|
if (value === "sdk" || value === "bearer") return null;
|
|
127
127
|
return "Invalid BEDROCK_AUTH_MODE value (must be one of: sdk, bearer)";
|
|
128
128
|
},
|
|
129
|
+
// Logical agent concurrency policy (agent scope) — the authoritative source
|
|
130
|
+
// that upsertSwarmConfigWithPolicyMirror mirrors into agents.maxTasks.
|
|
131
|
+
// Bounded to AgentSchema's maxTasks range [1, 100] so the mirrored column
|
|
132
|
+
// stays schema-valid.
|
|
133
|
+
AGENT_MAX_TASKS: (value) => {
|
|
134
|
+
const str = String(value).trim();
|
|
135
|
+
if (!/^\d+$/.test(str) || Number(str) < 1 || Number(str) > 100) {
|
|
136
|
+
return "Invalid AGENT_MAX_TASKS (must be an integer between 1 and 100)";
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
},
|
|
129
140
|
...booleanValidators([
|
|
141
|
+
"MULTI_RUNTIME_ENABLED",
|
|
130
142
|
"STEERING_ENABLED",
|
|
131
143
|
"MEMORY_HYBRID_SEARCH",
|
|
132
144
|
"MEMORY_GRAPH_EXPANSION",
|
|
@@ -150,6 +162,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
150
162
|
"OTEL_TRACE_POLL",
|
|
151
163
|
"ANONYMIZED_TELEMETRY",
|
|
152
164
|
"SWARM_HIDE_CLOUD_PROMO",
|
|
165
|
+
"DB_QUERY_BOUNDED_ENABLED",
|
|
153
166
|
]),
|
|
154
167
|
...enumValidator("SLACK_THREAD_STEERING", ["lead", "all"]),
|
|
155
168
|
...enumValidator("SLACK_THREAD_STEERING_MODE", ["steer", "queue"]),
|
|
@@ -167,8 +180,13 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
167
180
|
"WORKFLOW_MAX_ITERATIONS",
|
|
168
181
|
"WORKFLOW_MAX_STEPS_PER_RUN",
|
|
169
182
|
"SCHEDULER_INTERVAL_MS",
|
|
183
|
+
"RUNTIME_STALE_THRESHOLD_MIN",
|
|
170
184
|
"SCRIPT_RUN_CONCURRENCY_CAP",
|
|
171
185
|
"WORKER_API_READY_TIMEOUT_SECONDS",
|
|
186
|
+
"DB_QUERY_HTTP_BUDGET_MS",
|
|
187
|
+
"DB_QUERY_HTTP_MAX_ROWS",
|
|
188
|
+
"DB_QUERY_MCP_BUDGET_MS",
|
|
189
|
+
"DB_QUERY_MCP_MAX_ROWS",
|
|
172
190
|
],
|
|
173
191
|
1,
|
|
174
192
|
),
|
|
@@ -209,3 +209,57 @@ export async function awaitCredentials(opts: AwaitCredentialsOptions): Promise<C
|
|
|
209
209
|
}
|
|
210
210
|
return status;
|
|
211
211
|
}
|
|
212
|
+
|
|
213
|
+
/** Options for {@link retryBootStep}; injectable sleep/log keep tests deterministic. */
|
|
214
|
+
export interface RetryBootStepOptions {
|
|
215
|
+
/** Names the step in retry logs and the exhaustion error. */
|
|
216
|
+
label?: string;
|
|
217
|
+
attempts?: number;
|
|
218
|
+
initialDelayMs?: number;
|
|
219
|
+
maxDelayMs?: number;
|
|
220
|
+
sleep?: (ms: number) => Promise<void>;
|
|
221
|
+
log?: (line: string) => void;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Bounded retry for a recovery-critical boot step after the credential wait.
|
|
226
|
+
* A runtime that went stale mid-wait is only revived by a SUCCESSFUL
|
|
227
|
+
* registration, and the readiness report that follows is the only transition
|
|
228
|
+
* out of `waiting_for_credentials` — so unlike the runner's best-effort
|
|
229
|
+
* periodic paths, these steps retry with bounded backoff and propagate
|
|
230
|
+
* failure once exhausted. The caller decides what exhaustion means (the
|
|
231
|
+
* runner treats it like boot-registration failure rather than entering the
|
|
232
|
+
* work loop as though recovery succeeded).
|
|
233
|
+
*/
|
|
234
|
+
export async function retryBootStep(
|
|
235
|
+
step: () => Promise<void>,
|
|
236
|
+
opts: RetryBootStepOptions = {},
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
const label = opts.label ?? "boot step";
|
|
239
|
+
const attempts = opts.attempts ?? 5;
|
|
240
|
+
const sleep = opts.sleep ?? ((ms: number) => Bun.sleep(ms));
|
|
241
|
+
const log = opts.log ?? ((line: string) => console.warn(line));
|
|
242
|
+
const maxDelayMs = opts.maxDelayMs ?? 10_000;
|
|
243
|
+
let delayMs = opts.initialDelayMs ?? 1_000;
|
|
244
|
+
let lastError: unknown;
|
|
245
|
+
|
|
246
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
247
|
+
try {
|
|
248
|
+
await step();
|
|
249
|
+
return;
|
|
250
|
+
} catch (err) {
|
|
251
|
+
lastError = err;
|
|
252
|
+
if (attempt < attempts) {
|
|
253
|
+
log(
|
|
254
|
+
`[boot] ${label} failed (attempt ${attempt}/${attempts}, retry in ${delayMs}ms): ${err}`,
|
|
255
|
+
);
|
|
256
|
+
await sleep(delayMs);
|
|
257
|
+
delayMs = Math.min(delayMs * 2, maxDelayMs);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw lastError instanceof Error
|
|
263
|
+
? lastError
|
|
264
|
+
: new Error(`${label} failed after ${attempts} attempt(s): ${lastError}`);
|
|
265
|
+
}
|
|
@@ -61,7 +61,7 @@ export function CoreCredentialsStep({ state, goToNext, addLog }: StepProps) {
|
|
|
61
61
|
setTimeout(() => setReady(true), 1000);
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
generate();
|
|
64
|
+
generate().catch((err) => addLog(`Credential generation failed: ${err}`));
|
|
65
65
|
}, [state.services, addLog]);
|
|
66
66
|
|
|
67
67
|
useEffect(() => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { getApiKey } from "../utils/api-key.ts";
|
|
4
|
+
import { getMcpBaseUrl } from "../utils/constants.ts";
|
|
5
|
+
import { scrubSecrets } from "../utils/secret-scrubber.ts";
|
|
6
|
+
import {
|
|
7
|
+
auditProfileDivergences,
|
|
8
|
+
type FileReader,
|
|
9
|
+
type ProfileDivergence,
|
|
10
|
+
} from "./profile-sync.ts";
|
|
11
|
+
|
|
12
|
+
export interface ProfileSyncAuditConfig {
|
|
13
|
+
agentId: string;
|
|
14
|
+
apiUrl: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
claudeMdPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runProfileSyncAudit(
|
|
20
|
+
config: ProfileSyncAuditConfig,
|
|
21
|
+
deps: { fetchImpl?: typeof fetch; readFile?: FileReader } = {},
|
|
22
|
+
): Promise<{ agentId: string; divergences: ProfileDivergence[] }> {
|
|
23
|
+
const divergences = await auditProfileDivergences(
|
|
24
|
+
{ ...config, fetchImpl: deps.fetchImpl },
|
|
25
|
+
deps.readFile,
|
|
26
|
+
);
|
|
27
|
+
return { agentId: config.agentId, divergences };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function profileSyncAuditExitCode(divergences: ProfileDivergence[]): 0 | 2 {
|
|
31
|
+
return divergences.length > 0 ? 2 : 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (import.meta.main) {
|
|
35
|
+
const agentId = process.env.AGENT_ID;
|
|
36
|
+
const apiUrl = getMcpBaseUrl();
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
if (!agentId || !apiUrl || !apiKey) {
|
|
39
|
+
console.error("profile-sync-audit requires AGENT_ID, MCP_BASE_URL, and an API key");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await runProfileSyncAudit({
|
|
45
|
+
agentId,
|
|
46
|
+
apiUrl,
|
|
47
|
+
apiKey,
|
|
48
|
+
});
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
process.exit(profileSyncAuditExitCode(result.divergences));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
console.error(scrubSecrets(`profile-sync-audit failed: ${message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Harness-agnostic FS → DB profile sync (worker-side, HTTP-only).
|
|
3
3
|
*
|
|
4
4
|
* Persists an agent's self-editable identity / config files back to the API:
|
|
5
|
-
* - SOUL.md / IDENTITY.md / TOOLS.md / HEARTBEAT.md (
|
|
5
|
+
* - SOUL.md / IDENTITY.md / TOOLS.md / HEARTBEAT.md (independent POSTs)
|
|
6
6
|
* - ~/.claude/CLAUDE.md (claude POST)
|
|
7
7
|
* - /workspace/start-up.sh (agent-managed section) (setup POST)
|
|
8
8
|
*
|
|
@@ -27,8 +27,15 @@
|
|
|
27
27
|
* but it must be VISIBLE.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
import { resolveTemplateAsync } from "../prompts/resolver.ts";
|
|
31
|
+
import type { Agent, SwarmEvent } from "../types.ts";
|
|
30
32
|
import { MAX_PROFILE_FILE_LENGTH } from "../utils/constants.ts";
|
|
33
|
+
import {
|
|
34
|
+
type BudgetedIdentityField,
|
|
35
|
+
IDENTITY_FIELD_BUDGETS,
|
|
36
|
+
} from "../utils/identity-field-budget.ts";
|
|
31
37
|
import { scrubSecrets } from "../utils/secret-scrubber.ts";
|
|
38
|
+
import "./templates.ts";
|
|
32
39
|
|
|
33
40
|
export const SOUL_MD_PATH = "/workspace/SOUL.md";
|
|
34
41
|
export const IDENTITY_MD_PATH = "/workspace/IDENTITY.md";
|
|
@@ -56,6 +63,223 @@ export function contentSha256(content: string): string {
|
|
|
56
63
|
return new Bun.CryptoHasher("sha256").update(content).digest("hex");
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
export const PROFILE_MARKDOWN_FIELDS = [
|
|
67
|
+
"soulMd",
|
|
68
|
+
"identityMd",
|
|
69
|
+
"claudeMd",
|
|
70
|
+
"toolsMd",
|
|
71
|
+
"heartbeatMd",
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
export type ProfileMarkdownField = (typeof PROFILE_MARKDOWN_FIELDS)[number];
|
|
75
|
+
export type ProfileMarkdownSnapshot = Partial<
|
|
76
|
+
Record<ProfileMarkdownField, string | null | undefined>
|
|
77
|
+
>;
|
|
78
|
+
|
|
79
|
+
export interface ProfileDivergence {
|
|
80
|
+
field: ProfileMarkdownField;
|
|
81
|
+
diskSize: number | null;
|
|
82
|
+
dbSize: number;
|
|
83
|
+
budget: number | null;
|
|
84
|
+
delta: number | null;
|
|
85
|
+
diskHash: string | null;
|
|
86
|
+
dbHash: string;
|
|
87
|
+
diskMissing: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Pure disk-vs-DB comparison used by local profile-sync audits. */
|
|
91
|
+
export function findProfileDivergences(
|
|
92
|
+
disk: ProfileMarkdownSnapshot,
|
|
93
|
+
db: ProfileMarkdownSnapshot,
|
|
94
|
+
): ProfileDivergence[] {
|
|
95
|
+
const divergences: ProfileDivergence[] = [];
|
|
96
|
+
|
|
97
|
+
for (const field of PROFILE_MARKDOWN_FIELDS) {
|
|
98
|
+
const diskValue = disk[field];
|
|
99
|
+
const dbValue = db[field] ?? "";
|
|
100
|
+
const budget =
|
|
101
|
+
field === "heartbeatMd" ? null : IDENTITY_FIELD_BUDGETS[field as BudgetedIdentityField];
|
|
102
|
+
if (diskValue === undefined || diskValue === null) {
|
|
103
|
+
if (dbValue.length === 0) continue;
|
|
104
|
+
divergences.push({
|
|
105
|
+
field,
|
|
106
|
+
diskSize: null,
|
|
107
|
+
dbSize: dbValue.length,
|
|
108
|
+
budget,
|
|
109
|
+
delta: null,
|
|
110
|
+
diskHash: null,
|
|
111
|
+
dbHash: contentSha256(dbValue),
|
|
112
|
+
diskMissing: true,
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (diskValue === dbValue) continue;
|
|
117
|
+
|
|
118
|
+
divergences.push({
|
|
119
|
+
field,
|
|
120
|
+
diskSize: diskValue.length,
|
|
121
|
+
dbSize: dbValue.length,
|
|
122
|
+
budget,
|
|
123
|
+
delta: diskValue.length - dbValue.length,
|
|
124
|
+
diskHash: contentSha256(diskValue),
|
|
125
|
+
dbHash: contentSha256(dbValue),
|
|
126
|
+
diskMissing: false,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return divergences;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const PROFILE_FIELD_DETAILS: Record<
|
|
134
|
+
BudgetedIdentityField,
|
|
135
|
+
{ label: string; recoveryGuidance: string }
|
|
136
|
+
> = {
|
|
137
|
+
soulMd: {
|
|
138
|
+
label: `soulMd (${SOUL_MD_PATH})`,
|
|
139
|
+
recoveryGuidance: `Inspect and shrink the live ${SOUL_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${SOUL_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
|
|
140
|
+
},
|
|
141
|
+
identityMd: {
|
|
142
|
+
label: `identityMd (${IDENTITY_MD_PATH})`,
|
|
143
|
+
recoveryGuidance: `Inspect and shrink the live ${IDENTITY_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${IDENTITY_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
|
|
144
|
+
},
|
|
145
|
+
claudeMd: {
|
|
146
|
+
label:
|
|
147
|
+
"claudeMd (/workspace/CLAUDE.md for non-Claude harnesses; ~/.claude/CLAUDE.md for Claude)",
|
|
148
|
+
recoveryGuidance:
|
|
149
|
+
"Inspect and shrink the live CLAUDE.md source first, moving durable tail content into memory. After a worker restart, non-Claude harnesses can use /workspace/CLAUDE.md.pre-boot-<timestamp>.bak as the fallback copy; Claude's native ~/.claude/CLAUDE.md source is not archived by that boot step.",
|
|
150
|
+
},
|
|
151
|
+
toolsMd: {
|
|
152
|
+
label: `toolsMd (${TOOLS_MD_PATH})`,
|
|
153
|
+
recoveryGuidance: `Inspect and shrink the live ${TOOLS_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${TOOLS_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Render one persisted sync rejection through the prompt-template registry. */
|
|
158
|
+
export async function renderProfileSyncRejectionBanner(event: SwarmEvent): Promise<string | null> {
|
|
159
|
+
const data = event.data;
|
|
160
|
+
const field = data?.field;
|
|
161
|
+
const diskSize = data?.diskSize;
|
|
162
|
+
const dbSize = data?.dbSize;
|
|
163
|
+
const budget = data?.budget;
|
|
164
|
+
const delta = data?.delta;
|
|
165
|
+
if (
|
|
166
|
+
typeof field !== "string" ||
|
|
167
|
+
!(field in PROFILE_FIELD_DETAILS) ||
|
|
168
|
+
typeof diskSize !== "number" ||
|
|
169
|
+
typeof dbSize !== "number" ||
|
|
170
|
+
typeof budget !== "number" ||
|
|
171
|
+
typeof delta !== "number"
|
|
172
|
+
) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const details = PROFILE_FIELD_DETAILS[field as BudgetedIdentityField];
|
|
177
|
+
const result = await resolveTemplateAsync("task.profile_sync_rejection", {
|
|
178
|
+
timestamp: event.createdAt,
|
|
179
|
+
field_label: details.label,
|
|
180
|
+
disk_size: diskSize,
|
|
181
|
+
db_size: dbSize,
|
|
182
|
+
budget,
|
|
183
|
+
delta: delta >= 0 ? `+${delta}` : String(delta),
|
|
184
|
+
event_id: event.id,
|
|
185
|
+
recovery_guidance: details.recoveryGuidance,
|
|
186
|
+
});
|
|
187
|
+
return result.skipped ? null : result.text;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Fetch every field's latest unresolved rejection before a provider session. */
|
|
191
|
+
export async function fetchProfileSyncRejectionBanner(
|
|
192
|
+
config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
|
|
193
|
+
fetchImpl: typeof fetch = fetch,
|
|
194
|
+
readFile: FileReader = readFileIfExists,
|
|
195
|
+
): Promise<string> {
|
|
196
|
+
try {
|
|
197
|
+
const responses = await Promise.all(
|
|
198
|
+
(Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map(async (field) => {
|
|
199
|
+
const fetchLatestEvent = async (event: SwarmEvent["event"]): Promise<SwarmEvent | null> => {
|
|
200
|
+
const query = new URLSearchParams({
|
|
201
|
+
event,
|
|
202
|
+
agentId: config.agentId,
|
|
203
|
+
dataField: field,
|
|
204
|
+
limit: "1",
|
|
205
|
+
});
|
|
206
|
+
const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
|
|
207
|
+
headers: {
|
|
208
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
209
|
+
"X-Agent-ID": config.agentId,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) return null;
|
|
213
|
+
const payload = (await response.json()) as { events?: SwarmEvent[] };
|
|
214
|
+
return payload.events?.[0] ?? null;
|
|
215
|
+
};
|
|
216
|
+
const [rejection, reconciliation] = await Promise.all([
|
|
217
|
+
fetchLatestEvent("system.profile_sync_rejected"),
|
|
218
|
+
fetchLatestEvent("system.profile_sync_reconciled"),
|
|
219
|
+
]);
|
|
220
|
+
return { rejection, reconciliation };
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
const latestEvents = responses.flatMap(({ rejection, reconciliation }) =>
|
|
224
|
+
rejection ? [{ rejection, reconciliation }] : [],
|
|
225
|
+
);
|
|
226
|
+
if (latestEvents.length === 0) return "";
|
|
227
|
+
|
|
228
|
+
let profile: Record<string, unknown> | null = null;
|
|
229
|
+
const profileResponse = await fetchImpl(`${config.apiUrl}/me`, {
|
|
230
|
+
headers: {
|
|
231
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
232
|
+
"X-Agent-ID": config.agentId,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
if (profileResponse.ok) {
|
|
236
|
+
profile = (await profileResponse.json()) as Record<string, unknown>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let disk: Partial<Record<BudgetedIdentityField, string | undefined>> | null = null;
|
|
240
|
+
if (config.claudeMdPath) {
|
|
241
|
+
disk = {
|
|
242
|
+
soulMd: await readFile(SOUL_MD_PATH),
|
|
243
|
+
identityMd: await readFile(IDENTITY_MD_PATH),
|
|
244
|
+
claudeMd: await readFile(config.claudeMdPath),
|
|
245
|
+
toolsMd: await readFile(TOOLS_MD_PATH),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const unresolved = latestEvents.flatMap(({ rejection, reconciliation }) => {
|
|
250
|
+
if (reconciliation && reconciliation.createdAt > rejection.createdAt) return [];
|
|
251
|
+
const event = rejection;
|
|
252
|
+
const field = event.data?.field;
|
|
253
|
+
const rejectedDbHash = event.data?.dbHash;
|
|
254
|
+
const currentValue = typeof field === "string" ? profile?.[field] : undefined;
|
|
255
|
+
const diskValue =
|
|
256
|
+
typeof field === "string" ? disk?.[field as BudgetedIdentityField] : undefined;
|
|
257
|
+
if (typeof currentValue === "string" && diskValue === currentValue) return [];
|
|
258
|
+
return !(
|
|
259
|
+
typeof rejectedDbHash === "string" &&
|
|
260
|
+
typeof currentValue === "string" &&
|
|
261
|
+
contentSha256(currentValue) !== rejectedDbHash
|
|
262
|
+
)
|
|
263
|
+
? [event]
|
|
264
|
+
: [];
|
|
265
|
+
});
|
|
266
|
+
const banners = await Promise.all(unresolved.map(renderProfileSyncRejectionBanner));
|
|
267
|
+
return banners.filter((banner): banner is string => banner !== null).join("");
|
|
268
|
+
} catch {
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function prependProfileSyncRejectionBanner(
|
|
274
|
+
prompt: string,
|
|
275
|
+
config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
|
|
276
|
+
fetchImpl: typeof fetch = fetch,
|
|
277
|
+
readFile: FileReader = readFileIfExists,
|
|
278
|
+
): Promise<{ prompt: string; injected: boolean }> {
|
|
279
|
+
const banner = await fetchProfileSyncRejectionBanner(config, fetchImpl, readFile);
|
|
280
|
+
return banner ? { prompt: banner + prompt, injected: true } : { prompt, injected: false };
|
|
281
|
+
}
|
|
282
|
+
|
|
59
283
|
export async function writeIdentityBaselines(baselines: IdentityBaselines): Promise<void> {
|
|
60
284
|
await Bun.write(IDENTITY_BASELINES_PATH, JSON.stringify(baselines));
|
|
61
285
|
}
|
|
@@ -168,9 +392,23 @@ export function resolveClaudeMdPath(completedProviders: readonly string[]): stri
|
|
|
168
392
|
}
|
|
169
393
|
|
|
170
394
|
/** A single profile-update POST body, tagged with a label for logging. */
|
|
171
|
-
interface ProfilePayload {
|
|
395
|
+
export interface ProfilePayload {
|
|
172
396
|
label: string;
|
|
173
|
-
body: Record<string,
|
|
397
|
+
body: Record<string, string>;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Keep identity-file sync independent: one rejected ratcheting-budget write
|
|
402
|
+
* must not discard valid edits to other files from the same session.
|
|
403
|
+
*/
|
|
404
|
+
export function buildIndependentIdentityPayloads(
|
|
405
|
+
updates: Record<string, string>,
|
|
406
|
+
changeSource: ProfileChangeSource,
|
|
407
|
+
): ProfilePayload[] {
|
|
408
|
+
return Object.entries(updates).map(([field, value]) => ({
|
|
409
|
+
label: `identity.${field}`,
|
|
410
|
+
body: { [field]: value, changeSource },
|
|
411
|
+
}));
|
|
174
412
|
}
|
|
175
413
|
|
|
176
414
|
/**
|
|
@@ -229,8 +467,6 @@ export function buildIdentityPayload(
|
|
|
229
467
|
const content = files.soulMd;
|
|
230
468
|
if (baselines?.soulMd && contentSha256(content) === baselines.soulMd) {
|
|
231
469
|
// File unchanged during session — skip to preserve Lead's DB edits
|
|
232
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
233
|
-
warnProfileFileTooLarge(agentId, "soulMd", content.length);
|
|
234
470
|
} else if (content.trim()) {
|
|
235
471
|
if (content.length < IDENTITY_FILE_MIN_LENGTH) {
|
|
236
472
|
console.error(
|
|
@@ -246,8 +482,6 @@ export function buildIdentityPayload(
|
|
|
246
482
|
const content = files.identityMd;
|
|
247
483
|
if (baselines?.identityMd && contentSha256(content) === baselines.identityMd) {
|
|
248
484
|
// File unchanged during session — skip to preserve Lead's DB edits
|
|
249
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
250
|
-
warnProfileFileTooLarge(agentId, "identityMd", content.length);
|
|
251
485
|
} else if (content.trim()) {
|
|
252
486
|
if (content.length < IDENTITY_FILE_MIN_LENGTH) {
|
|
253
487
|
console.error(
|
|
@@ -263,8 +497,6 @@ export function buildIdentityPayload(
|
|
|
263
497
|
const content = files.toolsMd;
|
|
264
498
|
if (baselines?.toolsMd && contentSha256(content) === baselines.toolsMd) {
|
|
265
499
|
// File unchanged during session — skip
|
|
266
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
267
|
-
warnProfileFileTooLarge(agentId, "toolsMd", content.length);
|
|
268
500
|
} else if (content.trim()) {
|
|
269
501
|
updates.toolsMd = content;
|
|
270
502
|
}
|
|
@@ -298,6 +530,50 @@ async function readFileIfExists(path: string): Promise<string | undefined> {
|
|
|
298
530
|
}
|
|
299
531
|
}
|
|
300
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Compare the five local profile markdown files with the authenticated agent's
|
|
535
|
+
* stored DB values. This must run inside the agent's local environment; a
|
|
536
|
+
* central script cannot read another worker container's `/workspace` files.
|
|
537
|
+
*/
|
|
538
|
+
export async function auditProfileDivergences(
|
|
539
|
+
opts: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath" | "fetchImpl">,
|
|
540
|
+
readFile: FileReader = readFileIfExists,
|
|
541
|
+
): Promise<ProfileDivergence[]> {
|
|
542
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
543
|
+
const response = await doFetch(`${opts.apiUrl}/me`, {
|
|
544
|
+
headers: {
|
|
545
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
546
|
+
"X-Agent-ID": opts.agentId,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
if (!response.ok) {
|
|
550
|
+
throw new Error(`Profile divergence audit failed: HTTP ${response.status}`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const stored = (await response.json()) as Agent;
|
|
554
|
+
const claudeMdPath =
|
|
555
|
+
opts.claudeMdPath ??
|
|
556
|
+
(stored.harnessProvider
|
|
557
|
+
? stored.harnessProvider === "claude"
|
|
558
|
+
? CLAUDE_MD_PATH
|
|
559
|
+
: WORKSPACE_CLAUDE_MD_PATH
|
|
560
|
+
: null);
|
|
561
|
+
if (!claudeMdPath) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
"Profile divergence audit requires a registered harness provider or an explicit claudeMdPath",
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const disk: ProfileMarkdownSnapshot = {
|
|
567
|
+
soulMd: await readFile(SOUL_MD_PATH),
|
|
568
|
+
identityMd: await readFile(IDENTITY_MD_PATH),
|
|
569
|
+
claudeMd: await readFile(claudeMdPath),
|
|
570
|
+
toolsMd: await readFile(TOOLS_MD_PATH),
|
|
571
|
+
heartbeatMd: await readFile(HEARTBEAT_MD_PATH),
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
return findProfileDivergences(disk, stored);
|
|
575
|
+
}
|
|
576
|
+
|
|
301
577
|
/**
|
|
302
578
|
* Collect the profile-update POST bodies to send. Each entry is one POST.
|
|
303
579
|
* `fields` selects which groups to include. The file reader is injectable so
|
|
@@ -331,16 +607,12 @@ export async function collectProfilePayloads(
|
|
|
331
607
|
baselines,
|
|
332
608
|
agentId,
|
|
333
609
|
);
|
|
334
|
-
|
|
335
|
-
payloads.push({ label: "identity", body: { ...updates, changeSource } });
|
|
336
|
-
}
|
|
610
|
+
payloads.push(...buildIndependentIdentityPayloads(updates, changeSource));
|
|
337
611
|
}
|
|
338
612
|
|
|
339
613
|
if (fields.includes("claude")) {
|
|
340
614
|
const raw = await readFile(claudeMdPath);
|
|
341
|
-
if (raw
|
|
342
|
-
warnProfileFileTooLarge(agentId, "claudeMd", raw.length);
|
|
343
|
-
} else if (raw?.trim()) {
|
|
615
|
+
if (raw?.trim()) {
|
|
344
616
|
if (baselines?.claudeMd && contentSha256(raw) === baselines.claudeMd) {
|
|
345
617
|
// CLAUDE.md unchanged during session — skip to preserve Lead's DB edits
|
|
346
618
|
} else {
|