From 232b3db554c2c7cb900dfad770238a62e31fbbc5 Mon Sep 17 00:00:00 2001 From: Ry Walker Date: Fri, 31 Jul 2026 13:29:20 -0400 Subject: [PATCH] Revert "fix(agents): reuse Tembo sessions for follow-up changes (#345)" This reverts commit 4b7c00b698ee02af56e2b0f501164f1abc6b87e2. --- docs/src/content/docs/agent-lifecycle.md | 7 +- docs/src/content/docs/changelog.md | 5 - .../agents/[agent]/chat/actions.ts | 10 +- .../agents/[agent]/runs/[runId]/actions.ts | 10 +- web/src/lib/api-v1/actions.ts | 12 +- web/src/lib/cap-api.test.ts | 120 +----------------- web/src/lib/cap-api.ts | 107 ++-------------- web/src/lib/docs-content.ts | 2 +- web/src/lib/improvements-api.ts | 25 ---- 9 files changed, 25 insertions(+), 273 deletions(-) diff --git a/docs/src/content/docs/agent-lifecycle.md b/docs/src/content/docs/agent-lifecycle.md index c45568b6..fdced218 100644 --- a/docs/src/content/docs/agent-lifecycle.md +++ b/docs/src/content/docs/agent-lifecycle.md @@ -77,10 +77,7 @@ Open an agent and use the **chat** surface to probe its behavior against the live **draft** — chat always runs the draft, not stable. When something needs to change, submit a change request from the chat thread. TAS opens a PR via Tembo (same as new-agent authoring). Review and merge; the draft updates on your -default branch. Later change requests for that agent continue its existing Tembo -session, so the coding agent retains context from the earlier changes and can -resume its workspace instead of starting from scratch. If that session is no -longer available, TAS starts a new one automatically. +default branch. This is separate from **Improve the Agent** on a run, which anchors feedback to a specific execution — see [Improvements](/agent-studio/improvements/). @@ -97,4 +94,4 @@ writes the file back with a new commit. **Settings → Danger → Delete workspace** removes all workspace data (members, runs, schedules, connections, secrets, audit) but does **not** touch your GitHub repository or its agent files. Admin-only, type-to-confirm. See -[Settings](/agent-studio/settings/). +[Settings](/agent-studio/settings/). \ No newline at end of file diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md index 945d0d62..e79903fc 100644 --- a/docs/src/content/docs/changelog.md +++ b/docs/src/content/docs/changelog.md @@ -25,11 +25,6 @@ they are no longer release versions. Phase scope now lives in instant and local date/time fields for a requested IANA timezone, giving scheduled agents a reliable basis for relative windows and date-based dedup. -### Fixed -- **Agent changes reuse their Tembo coding session.** Later edits to the same - agent now continue the latest session, retaining prior change context and a - warm workspace. Missing old sessions fall back to creating a new one. - ## v2026.7.4 — Inbox reading view for digests, markdown context + run links, quieter Slack threads ### Added diff --git a/web/src/app/[workspace]/agents/[agent]/chat/actions.ts b/web/src/app/[workspace]/agents/[agent]/chat/actions.ts index 77b41eaf..fb3e0404 100644 --- a/web/src/app/[workspace]/agents/[agent]/chat/actions.ts +++ b/web/src/app/[workspace]/agents/[agent]/chat/actions.ts @@ -8,12 +8,11 @@ import { } from "@/lib/auth-server"; import { buildChatEditPrompt, - dispatchTemboTask, + createTemboTask, type CapError, } from "@/lib/cap-api"; import { createImprovement, - getLatestTemboTaskForAgent, improvementMarker, setImprovementCommitted, setImprovementTask, @@ -65,10 +64,6 @@ export async function chatSubmitAction(args: { }; } const canonicalName = agent.spec.name; - const existingTask = await getLatestTemboTaskForAgent( - workspace.id, - canonicalName, - ); const repo = await getWorkspaceRepo(workspace.id); if (!repo) { @@ -114,9 +109,8 @@ export async function chatSubmitAction(args: { )), }); - const res = await dispatchTemboTask({ + const res = await createTemboTask({ apiKey: temboCredential.apiKey, - existingTask, input: { prompt, repositoryUrl: `https://github.com/${repo.owner}/${repo.name}`, diff --git a/web/src/app/[workspace]/agents/[agent]/runs/[runId]/actions.ts b/web/src/app/[workspace]/agents/[agent]/runs/[runId]/actions.ts index 29ab3db3..9b9a33a0 100644 --- a/web/src/app/[workspace]/agents/[agent]/runs/[runId]/actions.ts +++ b/web/src/app/[workspace]/agents/[agent]/runs/[runId]/actions.ts @@ -9,12 +9,11 @@ import { } from "@/lib/auth-server"; import { buildImprovePrompt, - dispatchTemboTask, + createTemboTask, type CapError, } from "@/lib/cap-api"; import { createImprovement, - getLatestTemboTaskForAgent, improvementMarker, setImprovementCommitted, setImprovementTask, @@ -52,10 +51,6 @@ export async function improveAgentAction(args: { const run = await getRun(args.runId, workspace.id); if (!run || run.workspaceId !== workspace.id) notFound(); - const existingTask = await getLatestTemboTaskForAgent( - workspace.id, - run.agentName, - ); const repo = await getWorkspaceRepo(workspace.id); if (!repo) { @@ -99,9 +94,8 @@ export async function improveAgentAction(args: { defaultBranch: repo.defaultBranch, }); - const res = await dispatchTemboTask({ + const res = await createTemboTask({ apiKey: temboCredential.apiKey, - existingTask, input: { prompt, repositoryUrl: `https://github.com/${repo.owner}/${repo.name}`, diff --git a/web/src/lib/api-v1/actions.ts b/web/src/lib/api-v1/actions.ts index f7d27dc8..836d5ead 100644 --- a/web/src/lib/api-v1/actions.ts +++ b/web/src/lib/api-v1/actions.ts @@ -25,7 +25,7 @@ import { lookupUserByEmail, postMessage } from "@/lib/slack-api"; import { buildChatEditPrompt, buildCreateAgentPrompt, - dispatchTemboTask, + createTemboTask, } from "@/lib/cap-api"; import { resolveTemboCredential } from "@/lib/tembo-credentials"; import { buildPromptConnectionContext } from "@/lib/prompt-connections"; @@ -36,7 +36,6 @@ import { import { validateCron } from "@/lib/cron"; import { createImprovement, - getLatestTemboTaskForAgent, improvementMarker, setImprovementCommitted, setImprovementTask, @@ -346,10 +345,6 @@ export async function requestAgentChange( kind = "edit"; agentName = found.agent.spec.name; agentPath = found.agent.path; - const existingTask = await getLatestTemboTaskForAgent( - ctx.workspace.id, - agentName, - ); const row = await createImprovement({ workspaceId: ctx.workspace.id, runId: null, @@ -381,7 +376,6 @@ export async function requestAgentChange( prompt, kind, agentPath, - existingTask, }); } @@ -464,11 +458,9 @@ async function finishTask(args: { prompt: string; kind: "edit" | "create"; agentPath: string; - existingTask?: { taskId: string; htmlUrl: string } | null; }): Promise<{ ok: true; result: RequestAgentChangeResult } | ActionFailure> { - const res = await dispatchTemboTask({ + const res = await createTemboTask({ apiKey: args.apiKey, - existingTask: args.existingTask, input: { prompt: args.prompt, repositoryUrl: args.repositoryUrl, diff --git a/web/src/lib/cap-api.test.ts b/web/src/lib/cap-api.test.ts index e864d403..e38fef16 100644 --- a/web/src/lib/cap-api.test.ts +++ b/web/src/lib/cap-api.test.ts @@ -1,122 +1,12 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; -import { dispatchTemboTask, validateTemboApiKey } from "@/lib/cap-api"; +import { validateTemboApiKey } from "./cap-api"; -const input = { - prompt: "Update the agent instructions", - repositoryUrl: "https://github.com/tembo/example", - targetBranch: "main", -}; - -afterEach(() => { - vi.unstubAllEnvs(); - vi.restoreAllMocks(); - vi.unstubAllGlobals(); -}); - -describe("dispatchTemboTask", () => { - beforeEach(() => { - vi.stubEnv("TEMBO_API_URL", "https://api.example.test"); - vi.spyOn(console, "log").mockImplementation(() => undefined); - }); - - it("creates a new session when there is no prior agent task", async () => { - const fetchMock = vi.fn().mockResolvedValue( - Response.json({ - id: "new-session", - title: "Update agent", - status: "queued", - htmlUrl: "https://app.tembo.io/sessions/new-session", - }), - ); - vi.stubGlobal("fetch", fetchMock); - - const result = await dispatchTemboTask({ apiKey: "secret", input }); - - expect(result).toEqual({ - ok: true, - result: { - taskId: "new-session", - title: "Update agent", - status: "queued", - htmlUrl: "https://app.tembo.io/sessions/new-session", - }, - }); - expect(fetchMock).toHaveBeenCalledOnce(); - expect(fetchMock.mock.calls[0]?.[0]).toBe( - "https://api.example.test/public-api/session/create", - ); - }); - - it("submits a follow-up to the existing agent session", async () => { - const fetchMock = vi.fn().mockResolvedValue( - Response.json({ - message: { isQueued: true }, - session: { id: "existing-session", title: "Update agent" }, - }), - ); - vi.stubGlobal("fetch", fetchMock); - - const result = await dispatchTemboTask({ - apiKey: "secret", - input, - existingTask: { - taskId: "existing-session", - htmlUrl: "https://app.tembo.io/sessions/existing-session", - }, - }); - - expect(result).toEqual({ - ok: true, - result: { - taskId: "existing-session", - title: "Update agent", - status: "queued", - htmlUrl: "https://app.tembo.io/sessions/existing-session", - }, - }); - expect(fetchMock).toHaveBeenCalledOnce(); - expect(fetchMock.mock.calls[0]?.[0]).toBe( - "https://api.example.test/session/existing-session/message", - ); - expect(JSON.parse(fetchMock.mock.calls[0]?.[1]?.body as string)).toEqual({ - content: input.prompt, - }); - }); - - it("creates a new session when the prior Tembo session no longer exists", async () => { - const fetchMock = vi - .fn() - .mockResolvedValueOnce(new Response("missing", { status: 404 })) - .mockResolvedValueOnce( - Response.json({ - id: "replacement-session", - title: "Update agent", - status: "queued", - htmlUrl: "https://app.tembo.io/sessions/replacement-session", - }), - ); - vi.stubGlobal("fetch", fetchMock); - - const result = await dispatchTemboTask({ - apiKey: "secret", - input, - existingTask: { - taskId: "missing-session", - htmlUrl: "https://app.tembo.io/sessions/missing-session", - }, - }); - - expect(result.ok && result.result.taskId).toBe("replacement-session"); - expect(fetchMock).toHaveBeenCalledTimes(2); - expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([ - "https://api.example.test/session/missing-session/message", - "https://api.example.test/public-api/session/create", - ]); +describe("validateTemboApiKey", () => { + afterEach(() => { + vi.unstubAllGlobals(); }); -}); -describe("validateTemboApiKey", () => { it("returns the Tembo account identity for a valid key", async () => { const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ userId: "user-1", orgId: "org-1" }), { diff --git a/web/src/lib/cap-api.ts b/web/src/lib/cap-api.ts index 2dac0e1a..1664e7fa 100644 --- a/web/src/lib/cap-api.ts +++ b/web/src/lib/cap-api.ts @@ -10,13 +10,15 @@ import { import type { Framework } from "@/lib/agent-framework"; import type { CommitMode } from "@/lib/commit-mode-constants"; -// Thin client for the Tembo Coding Agent Platform task API. New sessions use -// /public-api/session/create; later changes can continue one through its -// /session/:id/message endpoint. Both authenticate with the workspace's Tembo -// API key as `Authorization: Bearer`. CAP renamed the public create mount from -// /public-api/task to /public-api/session with no alias -// (tembo/monorepo#9519, 2026-07-16); the old path falls through to a catch-all -// that 400s with {"error":{"message":"invalid request path"}}. +// Thin client for the Tembo Coding Agent Platform task API. The task +// endpoints live under the **/public-api** namespace and authenticate +// with the workspace's Tembo API key as `Authorization: Bearer`. POSTs +// a free-text prompt + repo URL to POST /public-api/session/create and +// returns a task record with an htmlUrl the user can follow; the task +// is what opens the PR. CAP renamed the mount from /public-api/task to +// /public-api/session with no alias (tembo/monorepo#9519, 2026-07-16); +// the old path falls through to a catch-all that 400s with +// {"error":{"message":"invalid request path"}}. const DEFAULT_TEMBO_API_URL = "https://api.tembo.io"; @@ -42,11 +44,6 @@ export interface CreateTaskResult { htmlUrl: string; } -export interface ExistingTemboTask { - taskId: string; - htmlUrl: string; -} - export type CapError = | { kind: "missing_tembo_key" } | { kind: "http"; status: number; body: string; url: string } @@ -95,9 +92,7 @@ export async function validateTemboApiKey( export async function createTemboTask(args: { apiKey: string; input: CreateTaskInput; -}): Promise< - { ok: true; result: CreateTaskResult } | { ok: false; error: CapError } -> { +}): Promise<{ ok: true; result: CreateTaskResult } | { ok: false; error: CapError }> { const baseUrl = process.env.TEMBO_API_URL ?? DEFAULT_TEMBO_API_URL; const body = { @@ -139,10 +134,7 @@ export async function createTemboTask(args: { // Status only — the response body can echo submitted content (#44). The // full body is still returned to the caller for handling, just not logged. console.log("[cap] ←", res.status); - return { - ok: false, - error: { kind: "http", status: res.status, body: text, url }, - }; + return { ok: false, error: { kind: "http", status: res.status, body: text, url } }; } const json = (await res.json()) as { @@ -162,83 +154,6 @@ export async function createTemboTask(args: { }; } -async function resumeTemboTask(args: { - apiKey: string; - prompt: string; - existingTask: ExistingTemboTask; -}): Promise< - { ok: true; result: CreateTaskResult } | { ok: false; error: CapError } -> { - const baseUrl = process.env.TEMBO_API_URL ?? DEFAULT_TEMBO_API_URL; - const url = `${baseUrl}/session/${args.existingTask.taskId}/message`; - console.log("[cap] POST", url); - - let res: Response; - try { - res = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${args.apiKey}`, - }, - cache: "no-store", - body: JSON.stringify({ content: args.prompt }), - }); - } catch (e) { - return { - ok: false, - error: { - kind: "network", - message: e instanceof Error ? e.message : String(e), - }, - }; - } - - if (!res.ok) { - const text = await res.text().catch(() => ""); - console.log("[cap] ←", res.status); - return { ok: false, error: { kind: "http", status: res.status, body: text, url } }; - } - - const json = (await res.json()) as { - message: { isQueued?: boolean }; - session: { id: string; title: string }; - }; - return { - ok: true, - result: { - taskId: json.session.id, - title: json.session.title, - status: json.message.isQueued === false ? "running" : "queued", - htmlUrl: args.existingTask.htmlUrl, - }, - }; -} - -/** Continue the latest CAP session for an existing agent when possible. */ -export async function dispatchTemboTask(args: { - apiKey: string; - input: CreateTaskInput; - existingTask?: ExistingTemboTask | null; -}): Promise<{ ok: true; result: CreateTaskResult } | { ok: false; error: CapError }> { - if (args.existingTask) { - const resumed = await resumeTemboTask({ - apiKey: args.apiKey, - prompt: args.input.prompt, - existingTask: args.existingTask, - }); - if ( - resumed.ok || - resumed.error.kind !== "http" || - resumed.error.status !== 404 - ) { - return resumed; - } - } - - return createTemboTask({ apiKey: args.apiKey, input: args.input }); -} - // Derive framework from the agent's repo path. Both callers // (chat-edit, run-improve) only have agentPath at the call site; the // framework is implicit in the directory. Falls back to pydantic for diff --git a/web/src/lib/docs-content.ts b/web/src/lib/docs-content.ts index 386c09d7..5acd44db 100644 --- a/web/src/lib/docs-content.ts +++ b/web/src/lib/docs-content.ts @@ -17,7 +17,7 @@ export const DOCS: Record = { "agent-lifecycle": { "title": "Agent lifecycle", "description": "Draft vs stable versions, promotion, ownership, chat-to-edit, pending agents, and restore.", - "body": "Agents move through a predictable lifecycle from first idea to production\nbehavior. This page covers the stages TAS tracks beyond what's in the Git file\nitself.\n\n## Draft and stable\n\nEvery agent has two runtime surfaces:\n\n| Surface | What it is | Used by |\n| ------- | ---------- | ------- |\n| **Draft** | The live file on your repo's default branch | The agent **chat** surface (probe and iterate) |\n| **Stable** | A numbered snapshot frozen in the database after promotion | **Run now**, schedules, [Slack](/agent-studio/slack-apps/), [webhooks](/agent-studio/automations-triggers/), and [triggers](/agent-studio/automations-triggers/) by default |\n\nUntil you promote a stable version, everything runs the draft. After promotion,\nautomated paths use stable for predictability while chat keeps running the draft\nso you can iterate without surprising production.\n\nSee [Core concepts → versioning](/agent-studio/core-concepts/) for the mental\nmodel.\n\n## Promoting to stable\n\nOn the agent detail page, **Promote to Stable (vN)** snapshots the current draft\nas the next numbered version and makes it the default for automated runs.\n\n- **Who can promote** — the agent's **owner**, or any workspace **admin**. An\n admin who isn't the owner sees a warning before confirming.\n- **When it's available** — only when the draft differs from the current stable\n (or no stable exists yet). A broken draft (parse errors) can't be promoted.\n- **What it records** — who promoted, when, and the version number. The\n **Versions** section lists every stable snapshot; compare them to see what\n changed when behavior shifted.\n\nSchedules can opt into running the **draft** instead of stable — useful for\ndogfooding before promotion.\n\n## Agent ownership\n\nEach agent can have an **owner** — a workspace member responsible for promoting\nchanges. Set or change the owner on the agent detail page. Ownership matters for:\n\n- The promote gate (owner or admin)\n- Knowing who to ask when an agent misbehaves\n- [Member detail](/agent-studio/dashboard-and-runs/#member-detail-admins) and\n offboarding checks\n\nAn agent with no assigned owner can still be promoted by any admin.\n\n## Creating agents\n\n### From chat (Tembo)\n\nDescribe a new agent; TAS hands the request to the\n[Tembo Coding Agent Platform](https://tembo.io) and opens a PR. Requires a Tembo\nAPI key in **Settings → Tembo Coding Agent**. See\n[Authoring agents](/agent-studio/authoring-agents/).\n\n### Pending agents\n\nWhile the PR is open, the agent appears in the **Agents** list with status\n**Pending**. Operators can **Dismiss** a pending create to stop tracking it in\nTAS — the GitHub PR is left alone and remains reachable from Tembo links.\n\n### By hand\n\nCommit a spec file directly to the connected repo under\n`agents/pydantic-agentspec/`. TAS picks it up on the next sync.\n\n## Chat-to-edit\n\nOpen an agent and use the **chat** surface to probe its behavior against the\nlive **draft** — chat always runs the draft, not stable. When something needs to\nchange, submit a change request from the chat thread. TAS opens a PR via Tembo\n(same as new-agent authoring). Review and merge; the draft updates on your\ndefault branch. Later change requests for that agent continue its existing Tembo\nsession, so the coding agent retains context from the earlier changes and can\nresume its workspace instead of starting from scratch. If that session is no\nlonger available, TAS starts a new one automatically.\n\nThis is separate from **Improve the Agent** on a run, which anchors feedback to a\nspecific execution — see [Improvements](/agent-studio/improvements/).\n\n## Deleting and restoring\n\n**Delete agent** removes the file from the connected repo (via a commit) and\nrecords the deletion in [Audit](/agent-studio/audit-and-roles/). Deleted agents\nappear under **Settings → Deleted agents** so you can **restore** them — restore\nwrites the file back with a new commit.\n\n## Workspace deletion\n\n**Settings → Danger → Delete workspace** removes all workspace data (members,\nruns, schedules, connections, secrets, audit) but does **not** touch your\nGitHub repository or its agent files. Admin-only, type-to-confirm. See\n[Settings](/agent-studio/settings/)." + "body": "Agents move through a predictable lifecycle from first idea to production\nbehavior. This page covers the stages TAS tracks beyond what's in the Git file\nitself.\n\n## Draft and stable\n\nEvery agent has two runtime surfaces:\n\n| Surface | What it is | Used by |\n| ------- | ---------- | ------- |\n| **Draft** | The live file on your repo's default branch | The agent **chat** surface (probe and iterate) |\n| **Stable** | A numbered snapshot frozen in the database after promotion | **Run now**, schedules, [Slack](/agent-studio/slack-apps/), [webhooks](/agent-studio/automations-triggers/), and [triggers](/agent-studio/automations-triggers/) by default |\n\nUntil you promote a stable version, everything runs the draft. After promotion,\nautomated paths use stable for predictability while chat keeps running the draft\nso you can iterate without surprising production.\n\nSee [Core concepts → versioning](/agent-studio/core-concepts/) for the mental\nmodel.\n\n## Promoting to stable\n\nOn the agent detail page, **Promote to Stable (vN)** snapshots the current draft\nas the next numbered version and makes it the default for automated runs.\n\n- **Who can promote** — the agent's **owner**, or any workspace **admin**. An\n admin who isn't the owner sees a warning before confirming.\n- **When it's available** — only when the draft differs from the current stable\n (or no stable exists yet). A broken draft (parse errors) can't be promoted.\n- **What it records** — who promoted, when, and the version number. The\n **Versions** section lists every stable snapshot; compare them to see what\n changed when behavior shifted.\n\nSchedules can opt into running the **draft** instead of stable — useful for\ndogfooding before promotion.\n\n## Agent ownership\n\nEach agent can have an **owner** — a workspace member responsible for promoting\nchanges. Set or change the owner on the agent detail page. Ownership matters for:\n\n- The promote gate (owner or admin)\n- Knowing who to ask when an agent misbehaves\n- [Member detail](/agent-studio/dashboard-and-runs/#member-detail-admins) and\n offboarding checks\n\nAn agent with no assigned owner can still be promoted by any admin.\n\n## Creating agents\n\n### From chat (Tembo)\n\nDescribe a new agent; TAS hands the request to the\n[Tembo Coding Agent Platform](https://tembo.io) and opens a PR. Requires a Tembo\nAPI key in **Settings → Tembo Coding Agent**. See\n[Authoring agents](/agent-studio/authoring-agents/).\n\n### Pending agents\n\nWhile the PR is open, the agent appears in the **Agents** list with status\n**Pending**. Operators can **Dismiss** a pending create to stop tracking it in\nTAS — the GitHub PR is left alone and remains reachable from Tembo links.\n\n### By hand\n\nCommit a spec file directly to the connected repo under\n`agents/pydantic-agentspec/`. TAS picks it up on the next sync.\n\n## Chat-to-edit\n\nOpen an agent and use the **chat** surface to probe its behavior against the\nlive **draft** — chat always runs the draft, not stable. When something needs to\nchange, submit a change request from the chat thread. TAS opens a PR via Tembo\n(same as new-agent authoring). Review and merge; the draft updates on your\ndefault branch.\n\nThis is separate from **Improve the Agent** on a run, which anchors feedback to a\nspecific execution — see [Improvements](/agent-studio/improvements/).\n\n## Deleting and restoring\n\n**Delete agent** removes the file from the connected repo (via a commit) and\nrecords the deletion in [Audit](/agent-studio/audit-and-roles/). Deleted agents\nappear under **Settings → Deleted agents** so you can **restore** them — restore\nwrites the file back with a new commit.\n\n## Workspace deletion\n\n**Settings → Danger → Delete workspace** removes all workspace data (members,\nruns, schedules, connections, secrets, audit) but does **not** touch your\nGitHub repository or its agent files. Admin-only, type-to-confirm. See\n[Settings](/agent-studio/settings/)." }, "api": { "title": "REST API", diff --git a/web/src/lib/improvements-api.ts b/web/src/lib/improvements-api.ts index 54ba0d4e..f29e7167 100644 --- a/web/src/lib/improvements-api.ts +++ b/web/src/lib/improvements-api.ts @@ -385,31 +385,6 @@ export async function listImprovementsForAgent( return res.rows.map(rowToImprovement); } -/** Latest CAP session that changed this agent, used for contextual follow-ups. */ -export async function getLatestTemboTaskForAgent( - workspaceId: string, - agentName: string, -): Promise<{ taskId: string; htmlUrl: string } | null> { - const res = await db.query<{ - tembo_task_id: string; - tembo_task_html_url: string; - }>( - `SELECT tembo_task_id, tembo_task_html_url - FROM improvement - WHERE workspace_id = $1 - AND agent_name = $2 - AND tembo_task_id IS NOT NULL - AND tembo_task_html_url IS NOT NULL - ORDER BY created_at DESC - LIMIT 1`, - [workspaceId, agentName], - ); - const row = res.rows[0]; - return row - ? { taskId: row.tembo_task_id, htmlUrl: row.tembo_task_html_url } - : null; -} - export async function listImprovementsForRun( runId: string, ): Promise {