Skip to content

Commit 5e6aec3

Browse files
committed
diwallows multiple workflows to run at the same time
1 parent f7bb80f commit 5e6aec3

5 files changed

Lines changed: 318 additions & 9 deletions

File tree

OPENCODE_UPGRADE_CHECKLIST.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ Use this whenever changing `opencode-ai`, `@opencode-ai/sdk`, `@opencode-ai/plug
3131
- [ ] `src/utils/session-abort.ts`: pending question replies, pending permission rejects, custom permission cleanup, and `session.abort`.
3232
- [ ] `src/utils/chat-usage.ts`: `session.messages()` shape, assistant `tokens`, `providerID`, `modelID`, `time.completed`, and last-synced message behavior.
3333
- [ ] `src/utils/chat-session.ts`: message part types, tool state statuses, pending input detection, and stale running tool normalization.
34+
- [ ] Pagination API for `session.messages()` in `src/utils/session-messages-page.ts`
3435

3536
## OpenCode Message And Permission Shapes
3637

3738
- [ ] Confirm `SessionStatusMap` still maps session id to `{ type: "busy" | "retry" | "idle" }`.
3839
- [ ] Confirm message containers still look like `{ info, parts }` for `session.messages()`.
40+
- [ ] Confirm `session.messages({ query: { limit: 1 } })` still returns the latest message first.
3941
- [ ] Confirm `ToolPart` still has `tool`, `callID`, `messageID`, and `state.status`.
4042
- [ ] Confirm tool states still use `pending`, `running`, `completed`, and `error`.
4143
- [ ] Confirm pending questions still expose `id`, `sessionID`, `questions`, and optional `tool.messageID` / `tool.callID`.
@@ -77,6 +79,7 @@ Known risky areas:
7779
- [ ] `python-protection.ts`: `command.execute.before`, `tool.execute.before`, `command`, `arguments`, `workdir`, `cwd`, `directory`, and `worktree`.
7880
- [ ] `honeytoken-protection.ts`: `tool.execute.before`, `tool.execute.after`, output shape, metadata shape, title shape, and input args visibility.
7981
- [ ] `skill-command-guard.ts`: `tool.execute.before`, `task` tool id, `command` arg shape, root session resolution, and backend skill metadata response.
82+
- [ ] `runWorkflow.ts`: latest-message concurrency guard, `client.session.messages({ limit: 1 })`, `runWorkflow` tool-part detection, and same-`callID` allowance.
8083
- [ ] Custom tools in `.opencode/plugins/*.ts`: `tool()` definition shape, `execute(args, context)`, return type, `context.sessionID`, `context.messageID`, `context.callID`, `context.ask`, `context.metadata`, and backend fetch behavior.
8184

8285
## Custom TeamCopilot Tools To Smoke Test
@@ -116,6 +119,8 @@ Known risky areas:
116119
- [ ] Trigger a custom TeamCopilot permission prompt and approve/reject it.
117120
- [ ] Run a prompt cronjob through planning, todo execution, user attention, resume, completion, and failure.
118121
- [ ] Run a workflow via `runWorkflow`.
122+
- [ ] Try one `runWorkflow` call and confirm it is allowed.
123+
- [ ] Try two `runWorkflow` calls in the same latest assistant message and confirm the second one is rejected.
119124
- [ ] Verify secret placeholder rewriting in shell commands without exposing plaintext in model-visible output.
120125
- [ ] Abort an active session and verify pending questions/permissions are cleaned up.
121126
- [ ] Sync usage and confirm token/cost rows are updated.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "teamcopilot",
3-
"version": "0.4.8",
3+
"version": "0.4.9",
44
"description": "A shared AI Agent for Teams",
55
"homepage": "https://teamcopilot.ai",
66
"repository": {

src/workspace_files/.opencode/plugins/runWorkflow.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type Plugin, tool } from "@opencode-ai/plugin"
2+
import type { OpencodeClient } from "@opencode-ai/sdk"
23

34
function getApiBaseUrl(): string {
45
const port = process.env.TEAMCOPILOT_PORT?.trim()
@@ -64,11 +65,41 @@ function sleep(ms: number): Promise<void> {
6465
return new Promise((resolve) => setTimeout(resolve, ms))
6566
}
6667

67-
interface SessionLookupResponse {
68-
error?: unknown
69-
data?: {
70-
id?: string
71-
parentID?: string
68+
async function rejectIfLatestMessageAlreadyRunningWorkflow(
69+
client: Pick<OpencodeClient, "session">,
70+
sessionID: string,
71+
currentCallID: string
72+
): Promise<void> {
73+
const response = await client.session.messages({
74+
path: {
75+
id: sessionID,
76+
},
77+
query: {
78+
limit: 1,
79+
},
80+
})
81+
82+
if (response.error) {
83+
throw new Error("Failed to load session messages while checking workflow concurrency.")
84+
}
85+
86+
const latestMessage = response.data?.[0] ?? null
87+
if (!latestMessage || latestMessage.info.role !== "assistant") {
88+
return
89+
}
90+
91+
const hasAnotherActiveRunWorkflow = latestMessage.parts.some((part) => {
92+
if (part.type !== "tool" || part.tool !== "runWorkflow") {
93+
return false
94+
}
95+
if (part.callID === currentCallID) {
96+
return false
97+
}
98+
return true
99+
})
100+
101+
if (hasAnotherActiveRunWorkflow) {
102+
throw new Error("You cannot run multiple workflows at the same time. Run them one by one, after each one completes.")
72103
}
73104
}
74105

@@ -81,7 +112,7 @@ export const RunWorkflowPlugin: Plugin = async ({ client }) => {
81112
path: {
82113
id: currentSessionID,
83114
},
84-
})) as SessionLookupResponse
115+
}))
85116
if (response.error) {
86117
throw new Error(`Failed to resolve root session for ${currentSessionID}`)
87118
}
@@ -131,6 +162,8 @@ export const RunWorkflowPlugin: Plugin = async ({ client }) => {
131162
throw new Error("Could not determine call id from tool context.")
132163
}
133164

165+
await rejectIfLatestMessageAlreadyRunningWorkflow(client, authSessionID, callId)
166+
134167
const startResponse = await fetch(`${getApiBaseUrl()}/api/workflows/execute`, {
135168
method: "POST",
136169
headers: {

0 commit comments

Comments
 (0)