@desplega.ai/agent-swarm 1.85.0 → 1.87.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 +1 -0
- package/openapi.json +72 -1
- package/package.json +10 -6
- package/src/be/db-queries/tracker.ts +21 -0
- package/src/be/db.ts +279 -14
- package/src/be/migrations/078_backfill_gpt_5_5_pricing.sql +15 -0
- package/src/be/migrations/079_task_followup_config.sql +1 -0
- package/src/be/modelsdev-cache.json +155618 -0
- package/src/be/modelsdev-cache.ts +46 -0
- package/src/be/seed-pricing.ts +7 -44
- package/src/cli.tsx +38 -2
- package/src/commands/codex-session-runner.ts +132 -0
- package/src/commands/context-preamble.ts +272 -0
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/e2b.ts +728 -0
- package/src/commands/provider-credentials.ts +10 -5
- package/src/commands/resume-session.ts +35 -78
- package/src/commands/runner.ts +128 -16
- package/src/e2b/dispatch.ts +429 -0
- package/src/e2b/env.ts +206 -0
- package/src/heartbeat/heartbeat.ts +145 -30
- package/src/heartbeat/templates.ts +11 -7
- package/src/http/session-data.ts +8 -1
- package/src/http/tasks.ts +152 -3
- package/src/jira/sync.ts +4 -4
- package/src/linear/sync.ts +6 -5
- package/src/prompts/base-prompt.ts +49 -3
- package/src/providers/claude-adapter.ts +76 -61
- package/src/providers/claude-managed-adapter.ts +61 -75
- package/src/providers/claude-managed-models.ts +18 -2
- package/src/providers/codex-adapter.ts +429 -112
- package/src/providers/codex-models.ts +9 -2
- package/src/providers/codex-oauth/auth-json.ts +18 -1
- package/src/providers/codex-oauth/flow.ts +24 -1
- package/src/providers/index.ts +28 -19
- package/src/providers/pricing-sources.md +7 -4
- package/src/providers/swarm-events-shared.ts +14 -0
- package/src/providers/types.ts +6 -0
- package/src/slack/HEURISTICS.md +5 -1
- package/src/slack/handlers.test.ts +35 -0
- package/src/slack/handlers.ts +79 -2
- package/src/tasks/worker-follow-up.ts +162 -2
- package/src/telemetry.ts +11 -1
- package/src/tests/base-prompt.test.ts +46 -8
- package/src/tests/claude-adapter.test.ts +5 -27
- package/src/tests/claude-managed-adapter.test.ts +42 -56
- package/src/tests/codex-adapter-otel.test.ts +4 -4
- package/src/tests/codex-adapter.test.ts +25 -37
- package/src/tests/codex-oauth.test.ts +149 -3
- package/src/tests/codex-pool.test.ts +14 -3
- package/src/tests/codex-swarm-events.test.ts +35 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/credential-check.test.ts +48 -29
- package/src/tests/e2b-dispatch.test.ts +330 -0
- package/src/tests/entrypoint-config-env-export.test.ts +81 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +165 -0
- package/src/tests/heartbeat-supersede-resume.test.ts +285 -0
- package/src/tests/heartbeat.test.ts +26 -16
- package/src/tests/migration-046-budgets.test.ts +6 -5
- package/src/tests/pricing-routes.test.ts +6 -5
- package/src/tests/prompt-template-remaining.test.ts +4 -0
- package/src/tests/provider-adapter.test.ts +10 -10
- package/src/tests/provider-command-format.test.ts +4 -4
- package/src/tests/resume-session.test.ts +42 -50
- package/src/tests/session-costs-codex-recompute.test.ts +25 -0
- package/src/tests/structured-output.test.ts +69 -0
- package/src/tests/task-completion-idempotency.test.ts +185 -2
- package/src/tests/task-supersede-resume.test.ts +722 -0
- package/src/tests/telemetry-init.test.ts +69 -0
- package/src/tests/vcs-tracking.test.ts +39 -0
- package/src/tools/send-task.ts +42 -10
- package/src/tools/store-progress.ts +2 -2
- package/src/tools/templates.ts +14 -2
- package/src/types.ts +46 -1
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/executors/agent-task.ts +3 -0
- package/templates/schedules/daily-blocker-digest/config.json +13 -0
- package/templates/schedules/daily-blocker-digest/content.md +150 -0
- package/templates/schedules/daily-compounding-reflection/config.json +21 -0
- package/templates/schedules/daily-compounding-reflection/content.md +210 -0
- package/templates/schedules/daily-hn-briefing/config.json +13 -0
- package/templates/schedules/daily-hn-briefing/content.md +97 -0
- package/templates/schedules/daily-workflow-health-audit/config.json +13 -0
- package/templates/schedules/daily-workflow-health-audit/content.md +189 -0
- package/templates/schedules/gtm-weekly-review/config.json +13 -0
- package/templates/schedules/gtm-weekly-review/content.md +58 -0
- package/templates/schedules/weekly-dependabot-triage/config.json +13 -0
- package/templates/schedules/weekly-dependabot-triage/content.md +45 -0
- package/templates/schema.ts +26 -0
- package/templates/skills/agentmail-sending/config.json +13 -0
- package/templates/skills/agentmail-sending/content.md +48 -0
- package/templates/skills/artifacts/config.json +13 -0
- package/templates/skills/artifacts/content.md +87 -0
- package/templates/skills/browser-use-cloud/config.json +13 -0
- package/templates/skills/browser-use-cloud/content.md +155 -0
- package/templates/skills/desloppify/config.json +13 -0
- package/templates/skills/desloppify/content.md +201 -0
- package/templates/skills/exa-search/config.json +13 -0
- package/templates/skills/exa-search/content.md +106 -0
- package/templates/skills/jira-interaction/config.json +13 -0
- package/templates/skills/jira-interaction/content.md +252 -0
- package/templates/skills/kapso-whatsapp/config.json +13 -0
- package/templates/skills/kapso-whatsapp/content.md +369 -0
- package/templates/skills/kv-storage/config.json +13 -0
- package/templates/skills/kv-storage/content.md +111 -0
- package/templates/skills/linear-interaction/config.json +20 -0
- package/templates/skills/linear-interaction/content.md +230 -0
- package/templates/skills/pages/config.json +18 -0
- package/templates/skills/pages/content.md +85 -0
- package/templates/skills/profile-corruption-escalation/config.json +13 -0
- package/templates/skills/profile-corruption-escalation/content.md +105 -0
- package/templates/skills/scheduled-task-resilience/config.json +13 -0
- package/templates/skills/scheduled-task-resilience/content.md +95 -0
- package/templates/skills/sprite-cli/config.json +13 -0
- package/templates/skills/sprite-cli/content.md +133 -0
- package/templates/skills/turso-interaction/config.json +13 -0
- package/templates/skills/turso-interaction/content.md +192 -0
- package/templates/skills/workflow-iterate/config.json +18 -0
- package/templates/skills/workflow-iterate/content.md +399 -0
- package/templates/skills/workflow-structured-output/config.json +13 -0
- package/templates/skills/workflow-structured-output/content.md +101 -0
- package/templates/skills/x-api-interactions/config.json +13 -0
- package/templates/skills/x-api-interactions/content.md +109 -0
- package/templates/workflows/autopilot/config.json +13 -0
- package/templates/workflows/autopilot/content.md +58 -0
- package/templates/workflows/linear-drain-loop/config.json +21 -0
- package/templates/workflows/linear-drain-loop/content.md +72 -0
- package/templates/workflows/ralph-loop/config.json +13 -0
- package/templates/workflows/ralph-loop/content.md +75 -0
package/README.md
CHANGED
|
@@ -124,6 +124,7 @@ Check [our templates](https://templates.agent-swarm.dev) for a quick start.
|
|
|
124
124
|
- **Workflow engine with Human-in-the-Loop** — DAG-based automation with approval gates, retries, and structured I/O. [Workflows →](https://docs.agent-swarm.dev/docs/concepts/workflows)
|
|
125
125
|
- **Scheduled & recurring tasks** — cron-based automation for standing work. [Scheduling →](https://docs.agent-swarm.dev/docs/concepts/scheduling)
|
|
126
126
|
- **Harness & LLM agnostic** — run with Claude Code, OpenAI Codex, pi-mono, Devin, Claude Managed Agents, raw LLMs, or opencode. [Harness config →](https://docs.agent-swarm.dev/docs/guides/harness-configuration) · [Add a new provider →](https://docs.agent-swarm.dev/docs/guides/harness-providers)
|
|
127
|
+
- **Follow-up continuity across all harnesses** — child tasks inherit bounded prior-task context even on providers without native session resume, while resumable providers still reuse prior sessions when possible. [Task lifecycle →](https://docs.agent-swarm.dev/docs/concepts/task-lifecycle)
|
|
127
128
|
- **Skills & MCP servers** — reusable procedural knowledge and per-agent MCP servers with scope cascade. [MCP tools →](https://docs.agent-swarm.dev/docs/reference/mcp-tools)
|
|
128
129
|
- **DB-backed pages** — agents publish HTML or JSON pages (reports, dashboards, action specs) via the `create_page` MCP tool with public / authed / password modes, version history, view counters, diff helpers, and PDF export. [MCP tools → Pages](https://docs.agent-swarm.dev/docs/reference/mcp-tools#pages-tools)
|
|
129
130
|
- **KV store** — Redis-like namespaced key/value store with auto-scoped context (Slack thread / PR / Linear issue / page). [MCP tools → KV](https://docs.agent-swarm.dev/docs/reference/mcp-tools#kv-tools)
|
package/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Swarm API",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.87.0",
|
|
6
6
|
"description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
|
@@ -7139,6 +7139,16 @@
|
|
|
7139
7139
|
"required": true,
|
|
7140
7140
|
"name": "taskId",
|
|
7141
7141
|
"in": "path"
|
|
7142
|
+
},
|
|
7143
|
+
{
|
|
7144
|
+
"schema": {
|
|
7145
|
+
"type": "integer",
|
|
7146
|
+
"minimum": 1,
|
|
7147
|
+
"maximum": 1000
|
|
7148
|
+
},
|
|
7149
|
+
"required": false,
|
|
7150
|
+
"name": "limit",
|
|
7151
|
+
"in": "query"
|
|
7142
7152
|
}
|
|
7143
7153
|
],
|
|
7144
7154
|
"responses": {
|
|
@@ -10114,6 +10124,67 @@
|
|
|
10114
10124
|
}
|
|
10115
10125
|
}
|
|
10116
10126
|
},
|
|
10127
|
+
"/api/tasks/{id}/supersede": {
|
|
10128
|
+
"post": {
|
|
10129
|
+
"summary": "Supersede an in-progress task (terminate + spawn resume follow-up)",
|
|
10130
|
+
"description": "Marks the original task `superseded` (terminal) and creates a fresh `taskType=\"resume\"` follow-up so a worker can pick up the work in a new provider session. Workflow-step tasks (those with `workflowRunStepId`) are carved out: the original is marked `failed` with reason `superseded_workflow_task` and no follow-up is created — the workflow engine's retry/failure policy applies.",
|
|
10131
|
+
"tags": [
|
|
10132
|
+
"Tasks"
|
|
10133
|
+
],
|
|
10134
|
+
"security": [
|
|
10135
|
+
{
|
|
10136
|
+
"bearerAuth": []
|
|
10137
|
+
}
|
|
10138
|
+
],
|
|
10139
|
+
"parameters": [
|
|
10140
|
+
{
|
|
10141
|
+
"schema": {
|
|
10142
|
+
"type": "string"
|
|
10143
|
+
},
|
|
10144
|
+
"required": true,
|
|
10145
|
+
"name": "id",
|
|
10146
|
+
"in": "path"
|
|
10147
|
+
}
|
|
10148
|
+
],
|
|
10149
|
+
"requestBody": {
|
|
10150
|
+
"content": {
|
|
10151
|
+
"application/json": {
|
|
10152
|
+
"schema": {
|
|
10153
|
+
"type": "object",
|
|
10154
|
+
"properties": {
|
|
10155
|
+
"reason": {
|
|
10156
|
+
"type": "string",
|
|
10157
|
+
"enum": [
|
|
10158
|
+
"graceful_shutdown",
|
|
10159
|
+
"context_limits",
|
|
10160
|
+
"manual_supersede",
|
|
10161
|
+
"crash_recovery"
|
|
10162
|
+
]
|
|
10163
|
+
}
|
|
10164
|
+
},
|
|
10165
|
+
"required": [
|
|
10166
|
+
"reason"
|
|
10167
|
+
]
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
}
|
|
10171
|
+
},
|
|
10172
|
+
"responses": {
|
|
10173
|
+
"200": {
|
|
10174
|
+
"description": "Task superseded (or workflow-failed)"
|
|
10175
|
+
},
|
|
10176
|
+
"400": {
|
|
10177
|
+
"description": "Task not in_progress"
|
|
10178
|
+
},
|
|
10179
|
+
"403": {
|
|
10180
|
+
"description": "Task belongs to another agent"
|
|
10181
|
+
},
|
|
10182
|
+
"404": {
|
|
10183
|
+
"description": "Task not found"
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
}
|
|
10187
|
+
},
|
|
10117
10188
|
"/api/tasks/{id}/vcs": {
|
|
10118
10189
|
"patch": {
|
|
10119
10190
|
"summary": "Update VCS (PR/MR) info for a task",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.87.0",
|
|
4
4
|
"description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "desplega.sh <contact@desplega.sh>",
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
"tsc:check": "bun tsc --noEmit",
|
|
46
46
|
"check:db-boundary": "bash scripts/check-db-boundary.sh",
|
|
47
47
|
"check:api-key-boundary": "bash scripts/check-api-key-boundary.sh",
|
|
48
|
+
"prepare-release": "bun scripts/prepare-release.ts",
|
|
49
|
+
"sync-chart-version": "bun scripts/sync-chart-version.ts",
|
|
50
|
+
"check-chart-version": "bun scripts/sync-chart-version.ts --check-if-package-version-changed",
|
|
48
51
|
"cli": "bun src/cli.tsx",
|
|
49
52
|
"hook": "bun src/hooks/hook.ts",
|
|
50
53
|
"claude": "bun src/cli.tsx claude",
|
|
@@ -108,12 +111,12 @@
|
|
|
108
111
|
"@desplega.ai/localtunnel": "^2.2.0",
|
|
109
112
|
"@inkjs/ui": "^2.0.0",
|
|
110
113
|
"@linear/sdk": "^77.0.0",
|
|
111
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
112
|
-
"@earendil-works/pi-ai": "^0.
|
|
113
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
114
|
+
"@earendil-works/pi-agent-core": "^0.76.0",
|
|
115
|
+
"@earendil-works/pi-ai": "^0.76.0",
|
|
116
|
+
"@earendil-works/pi-coding-agent": "^0.76.0",
|
|
114
117
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
115
|
-
"@openai/codex-sdk": "^0.
|
|
116
|
-
"@opencode-ai/sdk": "^1.15.
|
|
118
|
+
"@openai/codex-sdk": "^0.135.0",
|
|
119
|
+
"@opencode-ai/sdk": "^1.15.12",
|
|
117
120
|
"@openfort/openfort-node": "^0.9.1",
|
|
118
121
|
"@opentelemetry/api": "^1.9.1",
|
|
119
122
|
"@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
|
|
@@ -128,6 +131,7 @@
|
|
|
128
131
|
"ai": "^6.0.116",
|
|
129
132
|
"cron-parser": "^5.4.0",
|
|
130
133
|
"date-fns": "^4.1.0",
|
|
134
|
+
"e2b": "2.26.0",
|
|
131
135
|
"hono": "^4.12.3",
|
|
132
136
|
"ink": "^6.5.1",
|
|
133
137
|
"oauth4webapi": "^3.8.5",
|
|
@@ -110,6 +110,27 @@ export function updateTrackerSyncSwarmId(id: string, swarmId: string): void {
|
|
|
110
110
|
getDb().query("UPDATE tracker_sync SET swarmId = ? WHERE id = ?").run(swarmId, id);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Repoint ALL `tracker_sync` rows currently keyed to `oldSwarmId` to
|
|
115
|
+
* `newSwarmId`. Returns the number of rows updated.
|
|
116
|
+
*
|
|
117
|
+
* Used when a task is superseded (PR #594): the supersede parent becomes
|
|
118
|
+
* terminal but the Linear/Jira issue is still active, and outbound
|
|
119
|
+
* completion posts + inbound webhooks lookup by swarmId. Without
|
|
120
|
+
* repointing, the resume child's completion never makes it back to the
|
|
121
|
+
* tracker and subsequent inbound events load the terminal parent and
|
|
122
|
+
* create duplicates.
|
|
123
|
+
*
|
|
124
|
+
* Safe to call when no rows match (no-op, returns 0). Repoints across
|
|
125
|
+
* all providers (Linear AND Jira) and all entity types in one call.
|
|
126
|
+
*/
|
|
127
|
+
export function repointTrackerSyncBySwarmId(oldSwarmId: string, newSwarmId: string): number {
|
|
128
|
+
const result = getDb()
|
|
129
|
+
.query("UPDATE tracker_sync SET swarmId = ? WHERE swarmId = ?")
|
|
130
|
+
.run(newSwarmId, oldSwarmId);
|
|
131
|
+
return Number(result.changes ?? 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
export function createTrackerSync(data: {
|
|
114
135
|
provider: string;
|
|
115
136
|
entityType: "task";
|
package/src/be/db.ts
CHANGED
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
ContextSnapshotEventType,
|
|
30
30
|
ContextVersion,
|
|
31
31
|
CooldownConfig,
|
|
32
|
+
FollowUpConfig,
|
|
32
33
|
InboxItemState,
|
|
33
34
|
InboxItemStatus,
|
|
34
35
|
InboxItemType,
|
|
@@ -87,6 +88,7 @@ import type {
|
|
|
87
88
|
WorkflowSummary,
|
|
88
89
|
WorkflowVersion,
|
|
89
90
|
} from "../types";
|
|
91
|
+
import { FollowUpConfigSchema, isTerminalTaskStatus } from "../types";
|
|
90
92
|
import { deriveProviderFromKeyType } from "../utils/credentials";
|
|
91
93
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
92
94
|
import { decryptSecret, encryptSecret, getEncryptionKey, resolveEncryptionKey } from "./crypto";
|
|
@@ -993,6 +995,7 @@ type AgentTaskRow = {
|
|
|
993
995
|
workflowRunId: string | null;
|
|
994
996
|
workflowRunStepId: string | null;
|
|
995
997
|
outputSchema: string | null;
|
|
998
|
+
followUpConfig: string | null;
|
|
996
999
|
contextKey: string | null;
|
|
997
1000
|
createdAt: string;
|
|
998
1001
|
lastUpdatedAt: string;
|
|
@@ -1016,6 +1019,27 @@ type AgentTaskRow = {
|
|
|
1016
1019
|
};
|
|
1017
1020
|
|
|
1018
1021
|
function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
1022
|
+
let followUpConfig: FollowUpConfig | undefined;
|
|
1023
|
+
if (row.followUpConfig) {
|
|
1024
|
+
try {
|
|
1025
|
+
const parsed = FollowUpConfigSchema.safeParse(JSON.parse(row.followUpConfig));
|
|
1026
|
+
if (parsed.success) {
|
|
1027
|
+
followUpConfig = parsed.data;
|
|
1028
|
+
} else {
|
|
1029
|
+
console.warn(
|
|
1030
|
+
`[db] Ignoring invalid agent_tasks.followUpConfig for task ${row.id}:`,
|
|
1031
|
+
parsed.error.message,
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.warn(
|
|
1036
|
+
`[db] Ignoring malformed agent_tasks.followUpConfig for task ${row.id}:`,
|
|
1037
|
+
error instanceof Error ? error.message : String(error),
|
|
1038
|
+
);
|
|
1039
|
+
followUpConfig = undefined;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1019
1043
|
return {
|
|
1020
1044
|
id: row.id,
|
|
1021
1045
|
agentId: row.agentId,
|
|
@@ -1057,6 +1081,7 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
1057
1081
|
workflowRunId: row.workflowRunId ?? undefined,
|
|
1058
1082
|
workflowRunStepId: row.workflowRunStepId ?? undefined,
|
|
1059
1083
|
outputSchema: row.outputSchema ? JSON.parse(row.outputSchema) : undefined,
|
|
1084
|
+
followUpConfig,
|
|
1060
1085
|
contextKey: row.contextKey ?? undefined,
|
|
1061
1086
|
compactionCount: row.compactionCount ?? undefined,
|
|
1062
1087
|
peakContextPercent: row.peakContextPercent ?? undefined,
|
|
@@ -1173,7 +1198,7 @@ export const taskQueries = {
|
|
|
1173
1198
|
setProgress: () =>
|
|
1174
1199
|
getDb().prepare<AgentTaskRow, [string, string]>(
|
|
1175
1200
|
`UPDATE agent_tasks SET progress = ?,
|
|
1176
|
-
status = CASE WHEN status IN ('completed', 'failed', 'cancelled') THEN status ELSE 'in_progress' END,
|
|
1201
|
+
status = CASE WHEN status IN ('completed', 'failed', 'cancelled', 'superseded') THEN status ELSE 'in_progress' END,
|
|
1177
1202
|
lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
1178
1203
|
WHERE id = ? RETURNING *`,
|
|
1179
1204
|
),
|
|
@@ -1244,14 +1269,14 @@ export function startTask(taskId: string): AgentTask | null {
|
|
|
1244
1269
|
if (!oldTask) return null;
|
|
1245
1270
|
|
|
1246
1271
|
// Guard: never revive tasks that are already in a terminal state
|
|
1247
|
-
if (
|
|
1272
|
+
if (isTerminalTaskStatus(oldTask.status)) {
|
|
1248
1273
|
return null;
|
|
1249
1274
|
}
|
|
1250
1275
|
|
|
1251
1276
|
const row = getDb()
|
|
1252
1277
|
.prepare<AgentTaskRow, [string]>(
|
|
1253
1278
|
`UPDATE agent_tasks SET status = 'in_progress', lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
1254
|
-
WHERE id = ? AND status NOT IN ('completed', 'failed', 'cancelled') RETURNING *`,
|
|
1279
|
+
WHERE id = ? AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded') RETURNING *`,
|
|
1255
1280
|
)
|
|
1256
1281
|
.get(taskId);
|
|
1257
1282
|
if (row && oldTask) {
|
|
@@ -1291,6 +1316,31 @@ export function getChildTasks(parentTaskId: string): AgentTask[] {
|
|
|
1291
1316
|
.map(rowToAgentTask);
|
|
1292
1317
|
}
|
|
1293
1318
|
|
|
1319
|
+
/**
|
|
1320
|
+
* Returns true if `parentId` has at least one non-terminal child task with
|
|
1321
|
+
* `taskType = 'resume'`. Used by the heartbeat sweep as an idempotency guard:
|
|
1322
|
+
* if a prior sweep tick already created a resume follow-up for this parent,
|
|
1323
|
+
* don't create a duplicate.
|
|
1324
|
+
*
|
|
1325
|
+
* **Filters by taskType = 'resume'** specifically. A parent task can also
|
|
1326
|
+
* have ordinary non-terminal delegation children (`send-task` auto-defaults
|
|
1327
|
+
* `parentTaskId` to the caller's current task — see src/tools/send-task.ts).
|
|
1328
|
+
* Treating those as "already resumed" would incorrectly skip the resume
|
|
1329
|
+
* path for a crashed worker that had delegated subtasks (PR #594 review).
|
|
1330
|
+
*/
|
|
1331
|
+
export function hasNonTerminalResumeChild(parentId: string): boolean {
|
|
1332
|
+
const row = getDb()
|
|
1333
|
+
.prepare(
|
|
1334
|
+
`SELECT 1 FROM agent_tasks
|
|
1335
|
+
WHERE parentTaskId = ?
|
|
1336
|
+
AND taskType = 'resume'
|
|
1337
|
+
AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
|
|
1338
|
+
LIMIT 1`,
|
|
1339
|
+
)
|
|
1340
|
+
.get(parentId);
|
|
1341
|
+
return row !== undefined && row !== null;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1294
1344
|
export function updateTaskClaudeSessionId(
|
|
1295
1345
|
taskId: string,
|
|
1296
1346
|
claudeSessionId: string,
|
|
@@ -1370,14 +1420,18 @@ export function getTasksByStatus(status: AgentTaskStatus): AgentTask[] {
|
|
|
1370
1420
|
|
|
1371
1421
|
/**
|
|
1372
1422
|
* Find a task by VCS repo and issue/PR/MR number.
|
|
1373
|
-
* Returns the most recent non-
|
|
1423
|
+
* Returns the most recent non-terminal task for this VCS entity.
|
|
1424
|
+
*
|
|
1425
|
+
* Terminal exclusion MUST stay in lock-step with `TERMINAL_TASK_STATUSES`
|
|
1426
|
+
* in `src/types.ts`. SQL strings can't import a TS const — if you add a
|
|
1427
|
+
* new terminal status, grep for `NOT IN ('completed'` across this file.
|
|
1374
1428
|
*/
|
|
1375
1429
|
export function findTaskByVcs(vcsRepo: string, vcsNumber: number): AgentTask | null {
|
|
1376
1430
|
const row = getDb()
|
|
1377
1431
|
.prepare<AgentTaskRow, [string, number]>(
|
|
1378
1432
|
`SELECT * FROM agent_tasks
|
|
1379
1433
|
WHERE vcsRepo = ? AND vcsNumber = ?
|
|
1380
|
-
AND status NOT IN ('completed', 'failed')
|
|
1434
|
+
AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
|
|
1381
1435
|
ORDER BY createdAt DESC
|
|
1382
1436
|
LIMIT 1`,
|
|
1383
1437
|
)
|
|
@@ -1875,6 +1929,50 @@ export function findCompletedTaskInThread(
|
|
|
1875
1929
|
return row ? rowToAgentTask(row) : null;
|
|
1876
1930
|
}
|
|
1877
1931
|
|
|
1932
|
+
/**
|
|
1933
|
+
* Find the most recent CANCELLED task in a Slack thread. Used by the
|
|
1934
|
+
* follow-up re-delegation guard so a cancellation (worker SIGTERM,
|
|
1935
|
+
* runner-side abort, swarm-events tool-loop abort) doesn't permanently
|
|
1936
|
+
* jam re-dispatch when an earlier sibling task in the same thread also
|
|
1937
|
+
* completed.
|
|
1938
|
+
*
|
|
1939
|
+
* Matches both:
|
|
1940
|
+
* - `status = 'cancelled'` (the canonical terminal state from cancelTask)
|
|
1941
|
+
* - `status = 'failed'` with a failureReason that starts with "cancelled"
|
|
1942
|
+
* or "exit 130" or contains "cancelled" (the codex-adapter abort path
|
|
1943
|
+
* emits `failureReason: "cancelled"` and exits 130).
|
|
1944
|
+
*/
|
|
1945
|
+
export function findRecentCancelledTaskInThread(
|
|
1946
|
+
channelId: string,
|
|
1947
|
+
threadTs: string,
|
|
1948
|
+
windowMinutes: number,
|
|
1949
|
+
): AgentTask | null {
|
|
1950
|
+
const since = new Date(Date.now() - windowMinutes * 60 * 1000).toISOString();
|
|
1951
|
+
const row = getDb()
|
|
1952
|
+
.prepare<AgentTaskRow, [string, string, string]>(
|
|
1953
|
+
`SELECT * FROM agent_tasks
|
|
1954
|
+
WHERE slackChannelId = ?
|
|
1955
|
+
AND slackThreadTs = ?
|
|
1956
|
+
AND lastUpdatedAt > ?
|
|
1957
|
+
AND (
|
|
1958
|
+
status = 'cancelled'
|
|
1959
|
+
OR (
|
|
1960
|
+
status = 'failed'
|
|
1961
|
+
AND failureReason IS NOT NULL
|
|
1962
|
+
AND (
|
|
1963
|
+
failureReason LIKE 'cancelled%'
|
|
1964
|
+
OR failureReason LIKE 'exit 130%'
|
|
1965
|
+
OR failureReason LIKE '%cancelled%'
|
|
1966
|
+
)
|
|
1967
|
+
)
|
|
1968
|
+
)
|
|
1969
|
+
ORDER BY lastUpdatedAt DESC
|
|
1970
|
+
LIMIT 1`,
|
|
1971
|
+
)
|
|
1972
|
+
.get(channelId, threadTs, since);
|
|
1973
|
+
return row ? rowToAgentTask(row) : null;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1878
1976
|
export function completeTask(id: string, output?: string): AgentTask | null {
|
|
1879
1977
|
const oldTask = getTaskById(id);
|
|
1880
1978
|
if (!oldTask) return null;
|
|
@@ -1882,7 +1980,7 @@ export function completeTask(id: string, output?: string): AgentTask | null {
|
|
|
1882
1980
|
// Idempotency guard: don't re-complete a task already in a terminal state.
|
|
1883
1981
|
// Mirrors cancelTask. Prevents duplicate task.completed events, duplicate
|
|
1884
1982
|
// log entries, and duplicate follow-up tasks when multiple sessions race.
|
|
1885
|
-
if (
|
|
1983
|
+
if (isTerminalTaskStatus(oldTask.status)) {
|
|
1886
1984
|
return null;
|
|
1887
1985
|
}
|
|
1888
1986
|
|
|
@@ -1927,7 +2025,7 @@ export function failTask(id: string, reason: string): AgentTask | null {
|
|
|
1927
2025
|
// Idempotency guard: don't re-fail a task already in a terminal state.
|
|
1928
2026
|
// Mirrors cancelTask / completeTask. Prevents duplicate task.failed events
|
|
1929
2027
|
// and duplicate follow-up tasks when multiple sessions race.
|
|
1930
|
-
if (
|
|
2028
|
+
if (isTerminalTaskStatus(oldTask.status)) {
|
|
1931
2029
|
return null;
|
|
1932
2030
|
}
|
|
1933
2031
|
|
|
@@ -1964,8 +2062,7 @@ export function cancelTask(id: string, reason?: string): AgentTask | null {
|
|
|
1964
2062
|
if (!oldTask) return null;
|
|
1965
2063
|
|
|
1966
2064
|
// Only cancel tasks that are not already in a terminal state
|
|
1967
|
-
|
|
1968
|
-
if (terminalStatuses.includes(oldTask.status)) {
|
|
2065
|
+
if (isTerminalTaskStatus(oldTask.status)) {
|
|
1969
2066
|
return null;
|
|
1970
2067
|
}
|
|
1971
2068
|
|
|
@@ -1999,6 +2096,69 @@ export function cancelTask(id: string, reason?: string): AgentTask | null {
|
|
|
1999
2096
|
return row ? rowToAgentTask(row) : null;
|
|
2000
2097
|
}
|
|
2001
2098
|
|
|
2099
|
+
/**
|
|
2100
|
+
* Supersede a task: mark it as `superseded` (terminal) so a fresh "resume"
|
|
2101
|
+
* follow-up task can pick up where it left off. Used by the graceful-shutdown
|
|
2102
|
+
* path and the `POST /api/tasks/:id/supersede` route. Returns null if the task
|
|
2103
|
+
* is already terminal (mirrors `completeTask` / `cancelTask` idempotency).
|
|
2104
|
+
*
|
|
2105
|
+
* Writes a `task_superseded` agent_log with `{ reason, resumeTaskId }` payload
|
|
2106
|
+
* and emits a `task.superseded` workflow event. The caller is responsible for
|
|
2107
|
+
* creating the resume follow-up (via `createResumeFollowUp`) and passing the
|
|
2108
|
+
* resulting id as `resumeTaskId`.
|
|
2109
|
+
*/
|
|
2110
|
+
export function supersedeTask(
|
|
2111
|
+
id: string,
|
|
2112
|
+
args: { reason: string; resumeTaskId: string | null },
|
|
2113
|
+
): AgentTask | null {
|
|
2114
|
+
const oldTask = getTaskById(id);
|
|
2115
|
+
if (!oldTask) return null;
|
|
2116
|
+
|
|
2117
|
+
// Idempotency guard: don't re-supersede a task already in a terminal state.
|
|
2118
|
+
if (isTerminalTaskStatus(oldTask.status)) {
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const finishedAt = new Date().toISOString();
|
|
2123
|
+
const row = getDb()
|
|
2124
|
+
.prepare<AgentTaskRow, [string, string]>(
|
|
2125
|
+
`UPDATE agent_tasks
|
|
2126
|
+
SET status = 'superseded',
|
|
2127
|
+
finishedAt = ?,
|
|
2128
|
+
lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
2129
|
+
WHERE id = ? AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
|
|
2130
|
+
RETURNING *`,
|
|
2131
|
+
)
|
|
2132
|
+
.get(finishedAt, id);
|
|
2133
|
+
|
|
2134
|
+
if (row && oldTask) {
|
|
2135
|
+
try {
|
|
2136
|
+
createLogEntry({
|
|
2137
|
+
eventType: "task_superseded",
|
|
2138
|
+
taskId: id,
|
|
2139
|
+
agentId: row.agentId ?? undefined,
|
|
2140
|
+
oldValue: oldTask.status,
|
|
2141
|
+
newValue: "superseded",
|
|
2142
|
+
metadata: { reason: args.reason, resumeTaskId: args.resumeTaskId },
|
|
2143
|
+
});
|
|
2144
|
+
} catch {}
|
|
2145
|
+
try {
|
|
2146
|
+
import("../workflows/event-bus").then(({ workflowEventBus }) => {
|
|
2147
|
+
workflowEventBus.emit("task.superseded", {
|
|
2148
|
+
taskId: id,
|
|
2149
|
+
reason: args.reason,
|
|
2150
|
+
resumeTaskId: args.resumeTaskId,
|
|
2151
|
+
agentId: row.agentId,
|
|
2152
|
+
workflowRunId: row.workflowRunId,
|
|
2153
|
+
workflowRunStepId: row.workflowRunStepId,
|
|
2154
|
+
});
|
|
2155
|
+
});
|
|
2156
|
+
} catch {}
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
return row ? rowToAgentTask(row) : null;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2002
2162
|
/**
|
|
2003
2163
|
* Pause a task that is currently in progress.
|
|
2004
2164
|
* Used during graceful shutdown to allow tasks to resume after container restart.
|
|
@@ -2516,6 +2676,7 @@ export interface CreateTaskOptions {
|
|
|
2516
2676
|
* a schema'd task should be defensive about JSON parsing.
|
|
2517
2677
|
*/
|
|
2518
2678
|
outputSchema?: Record<string, unknown>;
|
|
2679
|
+
followUpConfig?: FollowUpConfig;
|
|
2519
2680
|
requestedByUserId?: string;
|
|
2520
2681
|
contextKey?: string;
|
|
2521
2682
|
}
|
|
@@ -2534,8 +2695,9 @@ export function findRecentSimilarTasks(opts: {
|
|
|
2534
2695
|
const conditions: string[] = ["createdAt > ?"];
|
|
2535
2696
|
const params: (string | number)[] = [since];
|
|
2536
2697
|
|
|
2537
|
-
// Exclude
|
|
2538
|
-
|
|
2698
|
+
// Exclude all terminal statuses — only active or recently created.
|
|
2699
|
+
// Keep in lock-step with `TERMINAL_TASK_STATUSES` in src/types.ts.
|
|
2700
|
+
conditions.push("status NOT IN ('completed', 'failed', 'cancelled', 'superseded')");
|
|
2539
2701
|
|
|
2540
2702
|
if (opts.creatorAgentId) {
|
|
2541
2703
|
conditions.push("creatorAgentId = ?");
|
|
@@ -2570,6 +2732,16 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2570
2732
|
if (options?.parentTaskId) {
|
|
2571
2733
|
const parent = getTaskById(options.parentTaskId);
|
|
2572
2734
|
if (parent) {
|
|
2735
|
+
// Identity & routing — anything that says "what work is this, who asked
|
|
2736
|
+
// for it, where does it run" carries forward to every child (follow-ups,
|
|
2737
|
+
// reboot retries, resume tasks). Explicit options always win.
|
|
2738
|
+
//
|
|
2739
|
+
// When adding a new identity-shaped column to `agent_tasks`, ADD IT HERE
|
|
2740
|
+
// unless you have a specific reason a child should NOT inherit it. This
|
|
2741
|
+
// is the single source of truth — `createResumeFollowUp` and the other
|
|
2742
|
+
// follow-up creators rely on this block instead of re-listing fields.
|
|
2743
|
+
|
|
2744
|
+
// Slack context
|
|
2573
2745
|
if (parent.slackChannelId && !options.slackChannelId) {
|
|
2574
2746
|
options.slackChannelId = parent.slackChannelId;
|
|
2575
2747
|
}
|
|
@@ -2579,18 +2751,98 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2579
2751
|
if (parent.slackUserId && !options.slackUserId) {
|
|
2580
2752
|
options.slackUserId = parent.slackUserId;
|
|
2581
2753
|
}
|
|
2754
|
+
|
|
2755
|
+
// AgentMail context
|
|
2582
2756
|
if (parent.agentmailInboxId && !options.agentmailInboxId) {
|
|
2583
2757
|
options.agentmailInboxId = parent.agentmailInboxId;
|
|
2584
2758
|
}
|
|
2759
|
+
if (parent.agentmailMessageId && !options.agentmailMessageId) {
|
|
2760
|
+
options.agentmailMessageId = parent.agentmailMessageId;
|
|
2761
|
+
}
|
|
2585
2762
|
if (parent.agentmailThreadId && !options.agentmailThreadId) {
|
|
2586
2763
|
options.agentmailThreadId = parent.agentmailThreadId;
|
|
2587
2764
|
}
|
|
2765
|
+
|
|
2766
|
+
// Mention context (Slack @-mentions)
|
|
2767
|
+
if (parent.mentionMessageId && !options.mentionMessageId) {
|
|
2768
|
+
options.mentionMessageId = parent.mentionMessageId;
|
|
2769
|
+
}
|
|
2770
|
+
if (parent.mentionChannelId && !options.mentionChannelId) {
|
|
2771
|
+
options.mentionChannelId = parent.mentionChannelId;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// VCS identity (GitHub / GitLab issue / PR / MR + webhook routing)
|
|
2775
|
+
// Webhook handlers locate active work via `findTaskByVcs(repo, number)`,
|
|
2776
|
+
// so a resume / follow-up child MUST carry the full VCS identity or
|
|
2777
|
+
// subsequent review/update events get dropped.
|
|
2778
|
+
if (parent.vcsProvider && !options.vcsProvider) {
|
|
2779
|
+
options.vcsProvider = parent.vcsProvider;
|
|
2780
|
+
}
|
|
2781
|
+
if (parent.vcsRepo && !options.vcsRepo) {
|
|
2782
|
+
options.vcsRepo = parent.vcsRepo;
|
|
2783
|
+
}
|
|
2784
|
+
if (parent.vcsNumber != null && options.vcsNumber == null) {
|
|
2785
|
+
options.vcsNumber = parent.vcsNumber;
|
|
2786
|
+
}
|
|
2787
|
+
if (parent.vcsEventType && !options.vcsEventType) {
|
|
2788
|
+
options.vcsEventType = parent.vcsEventType;
|
|
2789
|
+
}
|
|
2790
|
+
if (parent.vcsCommentId != null && options.vcsCommentId == null) {
|
|
2791
|
+
options.vcsCommentId = parent.vcsCommentId;
|
|
2792
|
+
}
|
|
2793
|
+
if (parent.vcsAuthor && !options.vcsAuthor) {
|
|
2794
|
+
options.vcsAuthor = parent.vcsAuthor;
|
|
2795
|
+
}
|
|
2796
|
+
if (parent.vcsUrl && !options.vcsUrl) {
|
|
2797
|
+
options.vcsUrl = parent.vcsUrl;
|
|
2798
|
+
}
|
|
2799
|
+
if (parent.vcsInstallationId != null && options.vcsInstallationId == null) {
|
|
2800
|
+
options.vcsInstallationId = parent.vcsInstallationId;
|
|
2801
|
+
}
|
|
2802
|
+
if (parent.vcsNodeId && !options.vcsNodeId) {
|
|
2803
|
+
options.vcsNodeId = parent.vcsNodeId;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
// Execution context (per-task overrides)
|
|
2807
|
+
//
|
|
2808
|
+
// `model` is DELIBERATELY NOT inherited. A parent task's `model` is a
|
|
2809
|
+
// concrete, provider-specific resolved string (e.g. `claude-opus-4-8`,
|
|
2810
|
+
// `openrouter/moonshotai/kimi-k2.6`). Derived tasks (resume follow-ups,
|
|
2811
|
+
// completion/review follow-ups, re-dispatches) routinely land on a
|
|
2812
|
+
// DIFFERENT agent — and therefore a different harness/provider — than the
|
|
2813
|
+
// parent. Carrying the parent's concrete model across that boundary makes
|
|
2814
|
+
// the child die at session-init with a model-incompatibility error before
|
|
2815
|
+
// any worker code runs (e.g. a `claude-opus-4-8` resume claimed by a Codex
|
|
2816
|
+
// worker → `400 model is not supported when using Codex`, or a
|
|
2817
|
+
// `kimi-k2.6` review follow-up routed to a Claude-harness Lead → session
|
|
2818
|
+
// exit 1). Per Taras's directive (2026-05-29): derived tasks must never
|
|
2819
|
+
// set the model — it resolves from the ASSIGNEE agent's own provider /
|
|
2820
|
+
// `MODEL_OVERRIDE` config at session-init (see
|
|
2821
|
+
// `src/commands/runner.ts` — `opts.model || configModel`). A null `model`
|
|
2822
|
+
// here is the correct, intended state. Do NOT re-add inheritance here; if
|
|
2823
|
+
// a same-provider child genuinely needs a specific model, the creator must
|
|
2824
|
+
// pass it explicitly.
|
|
2825
|
+
if (parent.dir && !options.dir) {
|
|
2826
|
+
options.dir = parent.dir;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// Contract (schema validation) — `store-progress` validates completion
|
|
2830
|
+
// output against `outputSchema`, runner injects structured-output
|
|
2831
|
+
// instructions only when it's present.
|
|
2832
|
+
if (parent.outputSchema && !options.outputSchema) {
|
|
2833
|
+
options.outputSchema = parent.outputSchema;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// Attribution
|
|
2588
2837
|
if (parent.requestedByUserId && !options.requestedByUserId) {
|
|
2589
2838
|
options.requestedByUserId = parent.requestedByUserId;
|
|
2590
2839
|
}
|
|
2591
2840
|
if (parent.contextKey && !options.contextKey) {
|
|
2592
2841
|
options.contextKey = parent.contextKey;
|
|
2593
2842
|
}
|
|
2843
|
+
if (parent.followUpConfig && !options.followUpConfig) {
|
|
2844
|
+
options.followUpConfig = parent.followUpConfig;
|
|
2845
|
+
}
|
|
2594
2846
|
}
|
|
2595
2847
|
}
|
|
2596
2848
|
|
|
@@ -2616,8 +2868,8 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2616
2868
|
vcsInstallationId, vcsNodeId,
|
|
2617
2869
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
2618
2870
|
mentionMessageId, mentionChannelId, dir, parentTaskId, model, scheduleId,
|
|
2619
|
-
workflowRunId, workflowRunStepId, outputSchema, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt
|
|
2620
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2871
|
+
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt
|
|
2872
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2621
2873
|
)
|
|
2622
2874
|
.get(
|
|
2623
2875
|
id,
|
|
@@ -2656,6 +2908,7 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2656
2908
|
options?.workflowRunId ?? null,
|
|
2657
2909
|
options?.workflowRunStepId ?? null,
|
|
2658
2910
|
options?.outputSchema ? JSON.stringify(options.outputSchema) : null,
|
|
2911
|
+
options?.followUpConfig ? JSON.stringify(options.followUpConfig) : null,
|
|
2659
2912
|
options?.requestedByUserId ?? null,
|
|
2660
2913
|
options?.contextKey ?? null,
|
|
2661
2914
|
pkg.version,
|
|
@@ -3973,6 +4226,15 @@ export const sessionLogQueries = {
|
|
|
3973
4226
|
"SELECT * FROM session_logs WHERE taskId = ? ORDER BY iteration ASC, lineNumber ASC",
|
|
3974
4227
|
),
|
|
3975
4228
|
|
|
4229
|
+
getRecentByTaskId: () =>
|
|
4230
|
+
getDb().prepare<SessionLogRow, [string, number]>(
|
|
4231
|
+
`SELECT * FROM (
|
|
4232
|
+
SELECT * FROM session_logs WHERE taskId = ?
|
|
4233
|
+
ORDER BY iteration DESC, lineNumber DESC
|
|
4234
|
+
LIMIT ?
|
|
4235
|
+
) ORDER BY iteration ASC, lineNumber ASC`,
|
|
4236
|
+
),
|
|
4237
|
+
|
|
3976
4238
|
getBySessionId: () =>
|
|
3977
4239
|
getDb().prepare<SessionLogRow, [string, number]>(
|
|
3978
4240
|
"SELECT * FROM session_logs WHERE sessionId = ? AND iteration = ? ORDER BY lineNumber ASC",
|
|
@@ -4008,7 +4270,10 @@ export function createSessionLogs(logs: {
|
|
|
4008
4270
|
})();
|
|
4009
4271
|
}
|
|
4010
4272
|
|
|
4011
|
-
export function getSessionLogsByTaskId(taskId: string): SessionLog[] {
|
|
4273
|
+
export function getSessionLogsByTaskId(taskId: string, limit?: number): SessionLog[] {
|
|
4274
|
+
if (typeof limit === "number" && limit > 0) {
|
|
4275
|
+
return sessionLogQueries.getRecentByTaskId().all(taskId, limit).map(rowToSessionLog);
|
|
4276
|
+
}
|
|
4012
4277
|
return sessionLogQueries.getByTaskId().all(taskId).map(rowToSessionLog);
|
|
4013
4278
|
}
|
|
4014
4279
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- 078_backfill_gpt_5_5_pricing.sql
|
|
2
|
+
-- Backfill Codex GPT-5.5 pricing into existing databases.
|
|
3
|
+
--
|
|
4
|
+
-- The vendored models.dev cache already contains gpt-5.5, and fresh server
|
|
5
|
+
-- boots seed it from src/be/seed-pricing.ts. Existing long-lived DBs can still
|
|
6
|
+
-- be missing those rows, which makes real gpt-5.5 Codex runs land as
|
|
7
|
+
-- costSource='unpriced'. Keep this migration idempotent so every environment
|
|
8
|
+
-- gets the baseline Standard-tier rates.
|
|
9
|
+
|
|
10
|
+
INSERT OR IGNORE INTO pricing
|
|
11
|
+
(provider, model, token_class, effective_from, price_per_million_usd, createdAt, lastUpdatedAt)
|
|
12
|
+
VALUES
|
|
13
|
+
('codex', 'gpt-5.5', 'input', 0, 5.0, 0, 0),
|
|
14
|
+
('codex', 'gpt-5.5', 'cached_input', 0, 0.5, 0, 0),
|
|
15
|
+
('codex', 'gpt-5.5', 'output', 0, 30.0, 0, 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE agent_tasks ADD COLUMN followUpConfig TEXT;
|