@taico/worker 0.0.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.
Files changed (40) hide show
  1. package/README.md +63 -0
  2. package/dist/Coordinator.d.ts +10 -0
  3. package/dist/Coordinator.js +183 -0
  4. package/dist/SocketIOTasksTransport.d.ts +68 -0
  5. package/dist/SocketIOTasksTransport.js +183 -0
  6. package/dist/Taico.d.ts +10 -0
  7. package/dist/Taico.js +69 -0
  8. package/dist/dev.d.ts +1 -0
  9. package/dist/dev.js +29 -0
  10. package/dist/formatters/ADKMessageFormatter.d.ts +4 -0
  11. package/dist/formatters/ADKMessageFormatter.js +32 -0
  12. package/dist/formatters/ClaudeMessageFormatter.d.ts +11 -0
  13. package/dist/formatters/ClaudeMessageFormatter.js +97 -0
  14. package/dist/formatters/OpencodeMessageFormatter.d.ts +8 -0
  15. package/dist/formatters/OpencodeMessageFormatter.js +56 -0
  16. package/dist/helpers/config.d.ts +5 -0
  17. package/dist/helpers/config.js +21 -0
  18. package/dist/helpers/ensureRepo.d.ts +1 -0
  19. package/dist/helpers/ensureRepo.js +27 -0
  20. package/dist/helpers/prepareWorkspace.d.ts +1 -0
  21. package/dist/helpers/prepareWorkspace.js +23 -0
  22. package/dist/helpers/sessionStore.d.ts +2 -0
  23. package/dist/helpers/sessionStore.js +34 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +5 -0
  26. package/dist/interfaces/AgentRunResult.d.ts +5 -0
  27. package/dist/interfaces/AgentRunResult.js +2 -0
  28. package/dist/runners/ADKAgentRunner.d.ts +13 -0
  29. package/dist/runners/ADKAgentRunner.js +65 -0
  30. package/dist/runners/AgentRunner.d.ts +43 -0
  31. package/dist/runners/AgentRunner.js +1 -0
  32. package/dist/runners/BaseAgentRunner.d.ts +13 -0
  33. package/dist/runners/BaseAgentRunner.js +27 -0
  34. package/dist/runners/ClaudeAgentRunner.d.ts +11 -0
  35. package/dist/runners/ClaudeAgentRunner.js +68 -0
  36. package/dist/runners/GitHubCopilotAgentRunner.d.ts +12 -0
  37. package/dist/runners/GitHubCopilotAgentRunner.js +81 -0
  38. package/dist/runners/OpenCodeAgentRunner.d.ts +25 -0
  39. package/dist/runners/OpenCodeAgentRunner.js +163 -0
  40. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Taico Agent Worker
2
+
3
+ This is the agent worker process. It connects to the Taico backend, listens for task events, and executes AI agents in isolated workspaces.
4
+
5
+ ## How It Works
6
+
7
+ The worker is a Node.js process that:
8
+
9
+ 1. Connects to the backend via REST API and WebSocket.
10
+ 2. Listens for task events that match the agent's status and tag triggers.
11
+ 3. When a matching task appears, it clones the project's git repo (if configured) into a clean workspace.
12
+ 4. Spins up the appropriate agent harness (Claude, OpenCode, ADK, or GitHub Copilot).
13
+ 5. The agent works on the task, posting comments and status updates back to the backend.
14
+
15
+ ## Architecture
16
+
17
+ The worker is intentionally decoupled from the backend. You can run workers anywhere — same machine, a different server, or in Kubernetes. Each worker represents one agent. To run multiple agents, start multiple worker processes.
18
+
19
+ The worker has access to whatever the host machine has access to. If you have Claude Code installed and authenticated, the Claude runner can use it. Same for OpenCode and GitHub Copilot.
20
+
21
+ ## Setup
22
+
23
+ ### 1. Create an Agent
24
+
25
+ In the Taico UI, go to **Agents** and create one (or use a pre-populated agent). Configure its system prompt, agent type, and status/tag triggers.
26
+
27
+ ### 2. Create an Access Token
28
+
29
+ From the agent's page in the UI, create an access token. The token needs at least these scopes: `task:*`, `meta:*`, `context:*`, `agents:read`, `mcp:use`.
30
+
31
+ > **Note:** Token-based auth is a temporary solution. Eventually, workers will securely impersonate agents automatically — no manual token management needed. The UI is intentionally minimal because this flow will be replaced.
32
+
33
+ ### 3. Configure the Worker
34
+
35
+ Create a `.env` file in this directory (one per agent). See `.env.example`:
36
+
37
+ ```env
38
+ AGENT_SLUG="claude" # Which agent this worker represents
39
+ BASE_URL="http://localhost:2000" # URL where the backend is running
40
+ ACCESS_TOKEN="your-token-here" # Token created in step 2
41
+ WORK_DIR="/absolute/path/to/workspace" # Folder where work happens (absolute path)
42
+ ```
43
+
44
+ ### 4. Start the Worker
45
+
46
+ ```bash
47
+ npm -w apps/worker run start
48
+ ```
49
+
50
+ To run multiple agents, create separate `.env` files (e.g., `.env.claude`, `.env.reviewer`) and start each worker with the appropriate env file loaded.
51
+
52
+ ## Supported Agent Types
53
+
54
+ | Type | Harness | Requirements |
55
+ |---|---|---|
56
+ | `claude` | Claude Agent SDK | Claude Code installed and authenticated |
57
+ | `opencode` | OpenCode SDK | OpenCode installed |
58
+ | `adk` | Google ADK | None (runs in-process). No tools — suited for general tasks like reading the board. |
59
+ | `githubcopilot` | GitHub Copilot SDK | GitHub Copilot set up on the machine |
60
+
61
+ ## MCP Integration
62
+
63
+ Each agent run gets access to a Taico MCP server at the backend's `/api/v1/tasks/tasks/mcp` endpoint. This gives agents tools to interact with Taico — reading tasks, posting comments, creating subtasks, etc. Authentication is handled automatically via the access token and a per-run ID header.
@@ -0,0 +1,10 @@
1
+ export declare class Coordinator {
2
+ private ready;
3
+ private transport;
4
+ private client;
5
+ constructor();
6
+ connect(): Promise<boolean>;
7
+ start(): Promise<boolean>;
8
+ private handleEvent;
9
+ private handleTask;
10
+ }
@@ -0,0 +1,183 @@
1
+ import { Taico } from "./Taico.js";
2
+ import { ACCESS_TOKEN, AGENT_SLUG, BASE_URL } from "./helpers/config.js";
3
+ import { prepareWorkspace } from "./helpers/prepareWorkspace.js";
4
+ import { getSession, setSession } from "./helpers/sessionStore.js";
5
+ import { ClaudeAgentRunner } from "./runners/ClaudeAgentRunner.js";
6
+ import { SocketIOTasksTransport } from "./SocketIOTasksTransport.js";
7
+ import { OpencodeAgentRunner } from "./runners/OpenCodeAgentRunner.js";
8
+ import { ADKAgentRunner } from "./runners/ADKAgentRunner.js";
9
+ import { GitHubCopilotAgentRunner } from "./runners/GitHubCopilotAgentRunner.js";
10
+ export class Coordinator {
11
+ ready = false;
12
+ transport;
13
+ client;
14
+ // Make transport
15
+ constructor() {
16
+ this.transport = new SocketIOTasksTransport(BASE_URL, ACCESS_TOKEN, {
17
+ namespace: '/tasks',
18
+ // debug: true,
19
+ });
20
+ this.client = new Taico(BASE_URL, ACCESS_TOKEN);
21
+ }
22
+ async connect() {
23
+ try {
24
+ await this.transport.start();
25
+ this.ready = true;
26
+ }
27
+ catch {
28
+ this.ready = false;
29
+ }
30
+ return this.ready;
31
+ }
32
+ async start() {
33
+ // Connect
34
+ if (!(await this.connect())) {
35
+ return false;
36
+ }
37
+ // Listen
38
+ this.transport.onTaskEvent(this.handleEvent);
39
+ return true;
40
+ }
41
+ handleEvent = async (evt) => {
42
+ // For now just look at create and assign and status change
43
+ if (evt.type === 'created' || evt.type === 'assigned' || evt.type === 'status_changed') {
44
+ console.log('--------------------------------------------------------');
45
+ console.log('Event received');
46
+ console.log(`- Type: ${evt.type}`);
47
+ console.log(`- Task: ${evt.task.name}`);
48
+ console.log(`- Actor: ${evt.actorId}`);
49
+ console.log(`- Task status: ${evt.task.status}`);
50
+ console.log(`- Task assignee: ${evt.task.assigneeActor?.id}`);
51
+ const task = evt.task;
52
+ if (task.assigneeActor?.id === evt.actorId) {
53
+ console.log(`- Update caused by assignee. Ignoring as this is a self event. ❌`);
54
+ return;
55
+ }
56
+ this.handleTask(task);
57
+ }
58
+ };
59
+ async handleTask(task) {
60
+ // Get the agent
61
+ const actor = task.assigneeActor;
62
+ if (!actor?.slug) {
63
+ console.log(`- Task ${task.id} not assigned or missing actor slug. Skipping. ❌`);
64
+ return;
65
+ }
66
+ const agent = await this.client.getAgent(actor.slug);
67
+ if (!agent) {
68
+ console.log(`- Agent @${actor.slug} not found. Skipping. ❌`);
69
+ return;
70
+ }
71
+ console.log(`- Agent: @${agent.slug}`);
72
+ if (agent.slug != AGENT_SLUG) {
73
+ console.log(`- We only react to @${AGENT_SLUG}. Skipping. ❌`);
74
+ return;
75
+ }
76
+ // Do we have runners for this agent?
77
+ if (agent.type !== "claude" && agent.type !== "opencode" && agent.type !== "adk" && agent.type !== "githubcopilot") {
78
+ console.log(`- Agent @${actor.slug} of type "${agent.type}" not supported. Skipping. ❌`);
79
+ return;
80
+ }
81
+ // Does the agent respond to this status?
82
+ if (!agent.statusTriggers.includes(task.status)) {
83
+ console.log(`- Agent @${agent.slug} doesn't react to status '${task.status}'. Skip. ❌`);
84
+ return;
85
+ }
86
+ // Extract project slug from tags and get project repo URL
87
+ let repoUrl = null;
88
+ const projectTag = task.tags?.find((tag) => tag.name.startsWith('project:'));
89
+ if (projectTag) {
90
+ const projectSlug = projectTag.name.replace('project:', '');
91
+ console.log(`- Found project tag: ${projectTag.name}, slug: ${projectSlug}`);
92
+ const project = await this.client.getProjectBySlug(projectSlug);
93
+ if (project) {
94
+ console.log(`- Project found: ${project.slug}`);
95
+ repoUrl = project.repoUrl ?? null;
96
+ if (repoUrl) {
97
+ console.log(`- Using project repo: ${repoUrl}`);
98
+ }
99
+ else {
100
+ console.log(`- Project has no repoUrl, using default`);
101
+ }
102
+ }
103
+ else {
104
+ console.log(`- Project not found for slug: ${projectSlug}`);
105
+ }
106
+ }
107
+ console.log(`- ✅ Conditions met. @${agent.slug} starting to work on task "${task.name}" 🦄`);
108
+ // Load session
109
+ const sessionId = getSession(agent.actorId, task.id);
110
+ // Prep workspace
111
+ const workDir = await prepareWorkspace(task.id, agent.actorId, repoUrl);
112
+ console.log(`- workspace prepped`);
113
+ const run = await this.client.startRun(task.id);
114
+ if (!run) {
115
+ console.error(`Failed to create a run ❌`);
116
+ return;
117
+ }
118
+ console.log(`Started Agent Run ID ${run.id}`);
119
+ // Create agent runner
120
+ let runner = null;
121
+ const modelConfig = {
122
+ providerId: agent.providerId ?? undefined,
123
+ modelId: agent.modelId ?? undefined,
124
+ };
125
+ if (agent.type === 'claude') {
126
+ runner = new ClaudeAgentRunner(modelConfig);
127
+ }
128
+ else if (agent.type === 'opencode') {
129
+ runner = new OpencodeAgentRunner(modelConfig);
130
+ }
131
+ else if (agent.type === 'adk') {
132
+ runner = new ADKAgentRunner(modelConfig);
133
+ }
134
+ else if (agent.type === 'githubcopilot') {
135
+ runner = new GitHubCopilotAgentRunner(modelConfig);
136
+ }
137
+ // This shouldn't happen because we checked first, but let's satisfy TypeScript
138
+ if (!runner) {
139
+ console.log(`- Agent @${actor.slug} of type "${agent.type}" not supported. Skipping. ❌`);
140
+ return;
141
+ }
142
+ try {
143
+ const results = await runner.run({
144
+ taskId: task.id,
145
+ prompt: `You got triggered by new activity in task "${task.id}". Fetch the task and proceed according to the following instructions.\n\n\n ${agent.systemPrompt}`,
146
+ cwd: workDir,
147
+ runId: run.id,
148
+ }, {
149
+ onEvent: (message) => {
150
+ console.log(`[agent message] ⤵️`);
151
+ console.log(message);
152
+ console.log('[end of agent message] ⤴️');
153
+ this.transport.publishActivity({
154
+ taskId: task.id,
155
+ message,
156
+ ts: Date.now(),
157
+ });
158
+ },
159
+ onSession: (sessionId) => {
160
+ if (!sessionId) {
161
+ setSession(agent.actorId, task.id, sessionId);
162
+ }
163
+ },
164
+ onError: (error) => {
165
+ console.log('Error detected');
166
+ console.log('error message:', error.message);
167
+ console.log('raw message', error.rawMessage);
168
+ // Post error to task as a comment
169
+ this.client.addComment(task.id, `⚠️ Error Detected ⚠️\n\n${error.message}\n\n\`\`\`json\nraw message\n${JSON.stringify(error.rawMessage, null, 2)}\n\`\`\``);
170
+ }
171
+ });
172
+ console.log(results);
173
+ // Force a comment
174
+ this.client.addComment(task.id, `Finished.\n\n${results.result}`);
175
+ }
176
+ catch (error) {
177
+ console.error(`Error running task`);
178
+ console.error(error);
179
+ // Force a comment
180
+ this.client.addComment(task.id, `❌ Something went wrong ❌\n\n${error}`);
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,68 @@
1
+ import { TaskWirePayload, CommentWirePayload } from "@taico/events";
2
+ export type TaskEvent = {
3
+ type: "created";
4
+ actorId: string;
5
+ task: TaskWirePayload;
6
+ } | {
7
+ type: "updated";
8
+ actorId: string;
9
+ task: TaskWirePayload;
10
+ } | {
11
+ type: "deleted";
12
+ actorId: string;
13
+ taskId: string;
14
+ } | {
15
+ type: "assigned";
16
+ actorId: string;
17
+ task: TaskWirePayload;
18
+ } | {
19
+ type: "status_changed";
20
+ actorId: string;
21
+ task: TaskWirePayload;
22
+ } | {
23
+ type: "commented";
24
+ actorId: string;
25
+ comment: CommentWirePayload;
26
+ };
27
+ export type TaskActivity = {
28
+ taskId: string;
29
+ kind?: string;
30
+ message: string;
31
+ ts: number;
32
+ };
33
+ export interface TasksTransport {
34
+ start(): Promise<void>;
35
+ stop(): Promise<void>;
36
+ onTaskEvent(handler: (evt: TaskEvent) => void): void;
37
+ publishActivity(activity: TaskActivity): void;
38
+ }
39
+ export declare class SocketIOTasksTransport implements TasksTransport {
40
+ private readonly baseUrl;
41
+ private readonly accessToken;
42
+ private readonly options?;
43
+ private socket?;
44
+ private handlers;
45
+ private started;
46
+ private reconnectAttempts;
47
+ private subscribeAckTimer?;
48
+ private awaitingSubscribeAck;
49
+ constructor(baseUrl: string, accessToken: string, options?: {
50
+ namespace?: string;
51
+ transports?: Array<"websocket" | "polling">;
52
+ autoSubscribe?: boolean;
53
+ debug?: boolean;
54
+ reconnection?: boolean;
55
+ reconnectionAttempts?: number;
56
+ reconnectionDelay?: number;
57
+ reconnectionDelayMax?: number;
58
+ randomizationFactor?: number;
59
+ subscribeAckTimeout?: number;
60
+ } | undefined);
61
+ onTaskEvent(handler: (evt: TaskEvent) => void): void;
62
+ publishActivity(activity: TaskActivity): void;
63
+ start(): Promise<void>;
64
+ stop(): Promise<void>;
65
+ private requestSubscribeAck;
66
+ private clearSubscribeAckTimer;
67
+ private emit;
68
+ }
@@ -0,0 +1,183 @@
1
+ /*
2
+ Socket.IO transport for task events + activity publishing.
3
+
4
+ Goal: keep Socket.IO specifics here, expose a clean TasksTransport interface.
5
+ */
6
+ import { io } from "socket.io-client";
7
+ import { TaskWireEvents, } from "@taico/events";
8
+ export class SocketIOTasksTransport {
9
+ baseUrl;
10
+ accessToken;
11
+ options;
12
+ socket;
13
+ handlers = [];
14
+ started = false;
15
+ reconnectAttempts = 0;
16
+ subscribeAckTimer;
17
+ awaitingSubscribeAck = false;
18
+ constructor(baseUrl, accessToken, options) {
19
+ this.baseUrl = baseUrl;
20
+ this.accessToken = accessToken;
21
+ this.options = options;
22
+ }
23
+ onTaskEvent(handler) {
24
+ this.handlers.push(handler);
25
+ }
26
+ publishActivity(activity) {
27
+ if (!this.socket || !this.socket.connected) {
28
+ if (this.options?.debug) {
29
+ console.warn("[tasks] publishActivity called while disconnected", activity);
30
+ }
31
+ return;
32
+ }
33
+ this.socket.emit(TaskWireEvents.TASK_ACTIVITY_POST, activity, (ack) => this.options?.debug && console.log(`[${TaskWireEvents.TASK_ACTIVITY_POST}] ack:`, ack));
34
+ }
35
+ async start() {
36
+ if (this.started)
37
+ return;
38
+ this.started = true;
39
+ const namespace = this.options?.namespace ?? "/tasks";
40
+ const transports = this.options?.transports ?? ["websocket"];
41
+ const autoSubscribe = this.options?.autoSubscribe ?? true;
42
+ const reconnection = this.options?.reconnection ?? true;
43
+ const reconnectionAttempts = this.options?.reconnectionAttempts ?? Infinity;
44
+ const reconnectionDelay = this.options?.reconnectionDelay ?? 1000;
45
+ const reconnectionDelayMax = this.options?.reconnectionDelayMax ?? 5000;
46
+ const randomizationFactor = this.options?.randomizationFactor ?? 0.5;
47
+ const url = `${this.baseUrl}${namespace}`;
48
+ if (this.options?.debug)
49
+ console.log(`[tasks] connecting to ${url}`);
50
+ this.socket = io(url, {
51
+ transports,
52
+ auth: { token: this.accessToken },
53
+ reconnection,
54
+ reconnectionAttempts,
55
+ reconnectionDelay,
56
+ reconnectionDelayMax,
57
+ randomizationFactor,
58
+ });
59
+ // ----- lifecycle -----
60
+ this.socket.on("connect", () => {
61
+ if (this.reconnectAttempts > 0) {
62
+ console.log(`[tasks] reconnected after ${this.reconnectAttempts} attempts:`, this.socket?.id);
63
+ }
64
+ else {
65
+ if (this.options?.debug)
66
+ console.log("[tasks] connected:", this.socket?.id);
67
+ }
68
+ // Reset reconnect attempts on successful connection
69
+ this.reconnectAttempts = 0;
70
+ if (autoSubscribe) {
71
+ this.requestSubscribeAck();
72
+ }
73
+ });
74
+ this.socket.on("disconnect", (reason) => {
75
+ if (this.options?.debug)
76
+ console.log("[tasks] disconnected:", reason);
77
+ this.clearSubscribeAckTimer();
78
+ this.awaitingSubscribeAck = false;
79
+ // Only log reconnection info if we're going to try reconnecting
80
+ if (reconnection && reason !== "io client disconnect") {
81
+ console.log("[tasks] will attempt to reconnect...");
82
+ }
83
+ });
84
+ this.socket.on("connect_error", (err) => {
85
+ this.reconnectAttempts++;
86
+ console.error(`[tasks] connect_error (attempt ${this.reconnectAttempts}):`, err?.message ?? err);
87
+ if (this.options?.debug)
88
+ console.error(err);
89
+ this.clearSubscribeAckTimer();
90
+ this.awaitingSubscribeAck = false;
91
+ });
92
+ this.socket.on("reconnect_attempt", (attemptNumber) => {
93
+ if (this.options?.debug)
94
+ console.log(`[tasks] reconnect_attempt ${attemptNumber}`);
95
+ });
96
+ this.socket.on("reconnect_failed", () => {
97
+ console.error("[tasks] reconnect_failed: max reconnection attempts reached");
98
+ });
99
+ // ----- events we care about -----
100
+ // Using shared wire event types from @taico/events
101
+ // These types ensure consistency between backend emission and frontend/agent reception
102
+ this.socket.on(TaskWireEvents.TASK_CREATED, (event) => this.emit({ type: "created", actorId: event.actor.id, task: event.payload }));
103
+ this.socket.on(TaskWireEvents.TASK_ASSIGNED, (event) => this.emit({ type: "assigned", actorId: event.actor.id, task: event.payload }));
104
+ this.socket.on(TaskWireEvents.TASK_STATUS_CHANGED, (event) => this.emit({ type: "status_changed", actorId: event.actor.id, task: event.payload }));
105
+ this.socket.on(TaskWireEvents.TASK_UPDATED, (event) => this.emit({ type: "updated", actorId: event.actor.id, task: event.payload }));
106
+ this.socket.on(TaskWireEvents.TASK_DELETED, (event) => this.emit({ type: "deleted", actorId: event.actor.id, taskId: event.payload.taskId }));
107
+ this.socket.on(TaskWireEvents.TASK_COMMENTED, (event) => this.emit({ type: "commented", actorId: event.actor.id, comment: event.payload }));
108
+ // Wait until first connect or error so callers can await start()
109
+ await new Promise((resolve, reject) => {
110
+ const onConnect = () => {
111
+ cleanup();
112
+ resolve();
113
+ };
114
+ const onErr = (e) => {
115
+ cleanup();
116
+ // don't permanently kill the transport; just reject start()
117
+ reject(e);
118
+ };
119
+ const cleanup = () => {
120
+ this.socket?.off("connect", onConnect);
121
+ this.socket?.off("connect_error", onErr);
122
+ };
123
+ this.socket?.once("connect", onConnect);
124
+ this.socket?.once("connect_error", onErr);
125
+ });
126
+ }
127
+ async stop() {
128
+ this.started = false;
129
+ if (!this.socket)
130
+ return;
131
+ const s = this.socket;
132
+ this.socket = undefined;
133
+ // remove listeners to avoid leaks if restarted
134
+ s.removeAllListeners();
135
+ s.disconnect();
136
+ this.clearSubscribeAckTimer();
137
+ this.awaitingSubscribeAck = false;
138
+ }
139
+ requestSubscribeAck() {
140
+ if (!this.socket)
141
+ return;
142
+ const timeoutMs = this.options?.subscribeAckTimeout ?? 5000;
143
+ const socket = this.socket;
144
+ this.clearSubscribeAckTimer();
145
+ this.awaitingSubscribeAck = true;
146
+ socket.emit("tasks.subscribe", {}, (ack) => {
147
+ if (this.socket !== socket)
148
+ return;
149
+ this.awaitingSubscribeAck = false;
150
+ this.clearSubscribeAckTimer();
151
+ if (this.options?.debug)
152
+ console.log("[tasks] subscribed:", ack);
153
+ });
154
+ this.subscribeAckTimer = setTimeout(() => {
155
+ if (!this.socket || this.socket !== socket)
156
+ return;
157
+ if (!this.awaitingSubscribeAck)
158
+ return;
159
+ this.awaitingSubscribeAck = false;
160
+ if (this.options?.debug) {
161
+ console.warn("[tasks] subscribe ack timeout; forcing reconnect");
162
+ }
163
+ this.socket.disconnect();
164
+ this.socket.connect();
165
+ }, timeoutMs);
166
+ }
167
+ clearSubscribeAckTimer() {
168
+ if (this.subscribeAckTimer) {
169
+ clearTimeout(this.subscribeAckTimer);
170
+ this.subscribeAckTimer = undefined;
171
+ }
172
+ }
173
+ emit(evt) {
174
+ for (const h of this.handlers) {
175
+ try {
176
+ h(evt);
177
+ }
178
+ catch (err) {
179
+ console.error("[tasks] handler error:", err);
180
+ }
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,10 @@
1
+ import { type AgentResponseDto, type AgentRunResponseDto, type ProjectResponseDto } from "@taico/client";
2
+ export declare class Taico {
3
+ constructor(baseUrl: string, accessToken: string);
4
+ getAgent(agentSlug: string): Promise<AgentResponseDto | null>;
5
+ getAgentPrompt(agentSlug: string): Promise<string>;
6
+ getAgentStatusTriggers(agentSlug: string): Promise<string[]>;
7
+ addComment(taskId: string, comment: string): Promise<void>;
8
+ getProjectBySlug(slug: string): Promise<ProjectResponseDto | null>;
9
+ startRun(taskId: string): Promise<AgentRunResponseDto | null>;
10
+ }
package/dist/Taico.js ADDED
@@ -0,0 +1,69 @@
1
+ // Taico.ts - API client wrapper using generated services
2
+ import { OpenAPI, ApiError, AgentService, TaskService, MetaProjectsService, AgentRunService, } from "@taico/client";
3
+ function isApiError(error) {
4
+ return error instanceof ApiError;
5
+ }
6
+ export class Taico {
7
+ constructor(baseUrl, accessToken) {
8
+ // Configure the generated client
9
+ OpenAPI.BASE = baseUrl;
10
+ OpenAPI.TOKEN = accessToken;
11
+ }
12
+ async getAgent(agentSlug) {
13
+ try {
14
+ return await AgentService.agentsControllerGetAgentBySlug(agentSlug);
15
+ }
16
+ catch (error) {
17
+ if (isApiError(error) && error.status === 404) {
18
+ return null;
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+ async getAgentPrompt(agentSlug) {
24
+ const agent = await this.getAgent(agentSlug);
25
+ const prompt = agent?.systemPrompt;
26
+ if (typeof prompt !== "string" || prompt.trim() === "") {
27
+ throw new Error(`[Taico] Agent @${agentSlug} has no systemPrompt.`);
28
+ }
29
+ return prompt;
30
+ }
31
+ async getAgentStatusTriggers(agentSlug) {
32
+ const agent = await this.getAgent(agentSlug);
33
+ const triggers = agent?.statusTriggers;
34
+ if (!Array.isArray(triggers) || triggers.some((t) => typeof t !== "string")) {
35
+ throw new Error(`[Taico] Agent @${agentSlug} has invalid statusTriggers (expected string[]).`);
36
+ }
37
+ return triggers;
38
+ }
39
+ async addComment(taskId, comment) {
40
+ try {
41
+ await TaskService.tasksControllerAddComment(taskId, { content: comment });
42
+ }
43
+ catch (error) {
44
+ console.error(`Failed to post comment to task ${taskId}:`, error);
45
+ }
46
+ }
47
+ async getProjectBySlug(slug) {
48
+ try {
49
+ return await MetaProjectsService.projectsControllerGetProjectBySlug(slug);
50
+ }
51
+ catch (error) {
52
+ if (isApiError(error) && error.status === 404) {
53
+ return null;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ async startRun(taskId) {
59
+ try {
60
+ return await AgentRunService.agentRunsControllerCreateAgentRun({
61
+ parentTaskId: taskId,
62
+ });
63
+ }
64
+ catch (error) {
65
+ console.error(`Failed to start run for task ${taskId}:`, error);
66
+ return null;
67
+ }
68
+ }
69
+ }
package/dist/dev.d.ts ADDED
@@ -0,0 +1 @@
1
+ import 'dotenv/config';
package/dist/dev.js ADDED
@@ -0,0 +1,29 @@
1
+ import 'dotenv/config';
2
+ import { OpencodeAgentRunner } from "./runners/OpenCodeAgentRunner.js";
3
+ async function dev() {
4
+ const modelConfig = {
5
+ providerId: 'spark-qwen3-coder-next-fp8',
6
+ modelId: 'Qwen/Qwen3-Coder-Next-FP8',
7
+ };
8
+ const taskId = '123';
9
+ const runId = 'xzy';
10
+ const runner = new OpencodeAgentRunner(modelConfig);
11
+ const ctx = {
12
+ taskId,
13
+ prompt: "Run pwd and tell me what you see",
14
+ cwd: `/Users/franciscogalarza/github/ai-monorepo/apps/worker/temp/asds`,
15
+ runId,
16
+ };
17
+ const callbacks = {
18
+ onEvent: (message) => {
19
+ console.log(message);
20
+ },
21
+ onError: (err) => {
22
+ console.error(err.message);
23
+ console.error(err.rawMessage);
24
+ }
25
+ };
26
+ await runner.run(ctx, callbacks);
27
+ return;
28
+ }
29
+ dev();
@@ -0,0 +1,4 @@
1
+ import { Event } from "@google/adk";
2
+ export declare class ADKMessageFormatter {
3
+ format(message: Event): Array<string>;
4
+ }
@@ -0,0 +1,32 @@
1
+ export class ADKMessageFormatter {
2
+ format(message) {
3
+ const content = message.content;
4
+ if (!content) {
5
+ return [];
6
+ }
7
+ const parts = content.parts;
8
+ if (!parts) {
9
+ return [];
10
+ }
11
+ const partMessages = parts.map(part => {
12
+ // Think
13
+ if (part.thought) {
14
+ return `💬 Thinking...`;
15
+ }
16
+ // Tool call
17
+ if (part.functionCall) {
18
+ return `🔧 Tool call: ${part.functionCall.name}`;
19
+ }
20
+ // Tool response
21
+ if (part.functionResponse) {
22
+ return `🔧 Tool response: ${part.functionResponse.name}`;
23
+ }
24
+ // Text
25
+ if (part.text) {
26
+ return `💬 Assistant: ${part.text}`;
27
+ }
28
+ return null;
29
+ }).filter(p => p != null);
30
+ return partMessages;
31
+ }
32
+ }
@@ -0,0 +1,11 @@
1
+ import { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ export declare class ClaudeMessageFormatter {
3
+ format(message: SDKMessage): string | null;
4
+ private formatAssistant;
5
+ private formatUser;
6
+ private formatResult;
7
+ private formatSystem;
8
+ private formatStreamEvent;
9
+ private formatToolProgress;
10
+ private formatAuthStatus;
11
+ }