opencode-ultracode 0.1.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/LICENSE +21 -0
- package/README.md +243 -0
- package/package.json +60 -0
- package/skills/workflow-authoring/SKILL.md +89 -0
- package/src/runtime/engine.ts +1020 -0
- package/src/runtime/schema.ts +92 -0
- package/src/runtime/script.ts +220 -0
- package/src/runtime/selftest.ts +321 -0
- package/src/server/index.ts +394 -0
- package/src/shared/format.ts +123 -0
- package/src/shared/state.ts +189 -0
- package/src/tui/index.tsx +1517 -0
- package/src/tui/requests.ts +265 -0
- package/src/tui/store.ts +266 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Shared types between the server plugin (engine) and the TUI plugin (views).
|
|
2
|
+
// The state file written by the server is the single source of truth the TUI polls.
|
|
3
|
+
|
|
4
|
+
import { createHash } from "node:crypto"
|
|
5
|
+
import { tmpdir } from "node:os"
|
|
6
|
+
import { basename } from "node:path"
|
|
7
|
+
|
|
8
|
+
export type RunStatus =
|
|
9
|
+
| "pending"
|
|
10
|
+
| "running"
|
|
11
|
+
| "paused"
|
|
12
|
+
| "completed"
|
|
13
|
+
| "failed"
|
|
14
|
+
| "stopped"
|
|
15
|
+
|
|
16
|
+
export type AgentStatus =
|
|
17
|
+
| "queued"
|
|
18
|
+
| "running"
|
|
19
|
+
| "completed"
|
|
20
|
+
| "failed"
|
|
21
|
+
| "cancelled"
|
|
22
|
+
|
|
23
|
+
export interface PhaseDef {
|
|
24
|
+
title: string
|
|
25
|
+
detail?: string
|
|
26
|
+
model?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PhaseState extends PhaseDef {
|
|
30
|
+
/** 1-based progress index shown in the left pane */
|
|
31
|
+
index: number
|
|
32
|
+
/** agent ids that belong to this phase, in spawn order */
|
|
33
|
+
agentIds: string[]
|
|
34
|
+
/** how many of those agents reached a terminal state */
|
|
35
|
+
done: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AgentActivity {
|
|
39
|
+
/** "tool" (default) for tool calls, "think" for a reasoning/thinking block */
|
|
40
|
+
kind?: "tool" | "think"
|
|
41
|
+
/** host tool-call id (when the host sends one) — used to match updates */
|
|
42
|
+
callId?: string
|
|
43
|
+
/** tool name, e.g. "StructuredOutput" or "bash"; "think" for reasoning entries */
|
|
44
|
+
tool: string
|
|
45
|
+
/** short title / first words of the call */
|
|
46
|
+
title: string
|
|
47
|
+
/** preview of the result (truncated) */
|
|
48
|
+
preview?: string
|
|
49
|
+
startedAt: number
|
|
50
|
+
endedAt?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AgentState {
|
|
54
|
+
id: string
|
|
55
|
+
/** display label, e.g. "tip:design" */
|
|
56
|
+
label: string
|
|
57
|
+
/** phase title this agent runs under */
|
|
58
|
+
phase: string
|
|
59
|
+
phaseIndex: number
|
|
60
|
+
status: AgentStatus
|
|
61
|
+
/** resolved model, e.g. "anthropic/claude-sonnet-4" or "qwen3.8" */
|
|
62
|
+
model: string
|
|
63
|
+
/**
|
|
64
|
+
* BILLED tokens: sum over every API call this agent made of
|
|
65
|
+
* input+output+reasoning+cache read/write. Each call re-sends the whole
|
|
66
|
+
* context, so this grows quadratically with tool-call count and is much
|
|
67
|
+
* larger than the context size. Matches `cost`.
|
|
68
|
+
*/
|
|
69
|
+
tokens: number
|
|
70
|
+
/**
|
|
71
|
+
* CONTEXT size: prompt tokens (input + cache read/write) of the LATEST API
|
|
72
|
+
* call, i.e. how big the agent's context actually is right now.
|
|
73
|
+
*/
|
|
74
|
+
contextTokens: number
|
|
75
|
+
outputTokens: number
|
|
76
|
+
cost: number
|
|
77
|
+
toolCalls: number
|
|
78
|
+
activity: AgentActivity[]
|
|
79
|
+
prompt: string
|
|
80
|
+
outcome?: unknown
|
|
81
|
+
/** raw final text (truncated for display) */
|
|
82
|
+
outcomeText?: string
|
|
83
|
+
/** latest LLM text chunk seen while the agent is running (live view) */
|
|
84
|
+
liveText?: string
|
|
85
|
+
/** recent live activity (text chunks, thinking, tool calls), newest last, capped */
|
|
86
|
+
liveFeed?: { at: number; kind: "text" | "tool" | "think"; text: string }[]
|
|
87
|
+
startedAt?: number
|
|
88
|
+
endedAt?: number
|
|
89
|
+
error?: string
|
|
90
|
+
sessionId?: string
|
|
91
|
+
/** true when this agent's result was replayed from a previous run of the same runId (resume) */
|
|
92
|
+
replayed?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RunLogEntry {
|
|
96
|
+
at: number
|
|
97
|
+
message: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface RunState {
|
|
101
|
+
runId: string
|
|
102
|
+
status: RunStatus
|
|
103
|
+
/** workflow meta */
|
|
104
|
+
name: string
|
|
105
|
+
description: string
|
|
106
|
+
whenToUse?: string
|
|
107
|
+
/** phases in display order (from meta.phases, plus any discovered) */
|
|
108
|
+
phases: PhaseState[]
|
|
109
|
+
/** every agent, in spawn order */
|
|
110
|
+
agents: Record<string, AgentState>
|
|
111
|
+
/** agent ids in spawn order */
|
|
112
|
+
agentOrder: string[]
|
|
113
|
+
logs: RunLogEntry[]
|
|
114
|
+
/** total agents spawned so far */
|
|
115
|
+
agentCount: number
|
|
116
|
+
/** agents that reached a terminal state */
|
|
117
|
+
agentDone: number
|
|
118
|
+
startedAt: number
|
|
119
|
+
endedAt?: number
|
|
120
|
+
/** total BILLED tokens across all agents (see AgentState.tokens) */
|
|
121
|
+
totalTokens: number
|
|
122
|
+
/** sum of every agent's current context size (see AgentState.contextTokens) */
|
|
123
|
+
totalContextTokens: number
|
|
124
|
+
totalCost: number
|
|
125
|
+
/** resolved script path (for save) */
|
|
126
|
+
scriptPath?: string
|
|
127
|
+
/** the workflow's final return value (stringified for storage) */
|
|
128
|
+
result?: string
|
|
129
|
+
/** error message if failed */
|
|
130
|
+
error?: string
|
|
131
|
+
/** the directory this run was created in */
|
|
132
|
+
directory: string
|
|
133
|
+
/** opencode session that started the run; the result turn is delivered here (also after a resume) */
|
|
134
|
+
mainSessionID?: string
|
|
135
|
+
/** model the starting session used; agents without an explicit model inherit it (needed to resume) */
|
|
136
|
+
defaultModel?: string
|
|
137
|
+
/** the script's `args` value, kept so a resume re-runs the script with the same input */
|
|
138
|
+
args?: unknown
|
|
139
|
+
/** set when the run was resumed after its engine died (opencode exit/crash) */
|
|
140
|
+
resumedAt?: number
|
|
141
|
+
/** how many times the run has been resumed */
|
|
142
|
+
resumeCount?: number
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* control.json written by the TUI. A live engine polls it for pause/resume/stop.
|
|
147
|
+
* `resume` on a run with no live engine (opencode exited while it ran) asks the
|
|
148
|
+
* server plugin to restart the run: completed agents replay from the journal,
|
|
149
|
+
* the rest run again.
|
|
150
|
+
*/
|
|
151
|
+
export interface ControlState {
|
|
152
|
+
action?: "pause" | "resume" | "stop"
|
|
153
|
+
pause?: boolean
|
|
154
|
+
resume?: boolean
|
|
155
|
+
stop?: boolean
|
|
156
|
+
at?: number
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- wire helpers -----------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Where run artifacts (state.json, journal.jsonl, script.js, control.json) live:
|
|
163
|
+
* /tmp/opencode-workflows/<project-name>-<hash6>. Runs are scratch data, so they
|
|
164
|
+
* stay out of the project tree; the hash suffix keeps two projects with the same
|
|
165
|
+
* folder name (e.g. two "api" checkouts) from sharing a run list.
|
|
166
|
+
* Saved workflows (<name>.js) are project assets and stay under workflowRoot().
|
|
167
|
+
*/
|
|
168
|
+
export function runsRoot(worktree: string): string {
|
|
169
|
+
const base = process.platform === "win32" ? tmpdir() : "/tmp"
|
|
170
|
+
const name = (basename(worktree) || "project").replace(/[^a-zA-Z0-9._-]/g, "-")
|
|
171
|
+
const hash = createHash("sha1").update(worktree).digest("hex").slice(0, 6)
|
|
172
|
+
return `${base}/opencode-workflows/${name}-${hash}`
|
|
173
|
+
}
|
|
174
|
+
export function runDir(runsRootDir: string, runId: string): string {
|
|
175
|
+
return `${runsRootDir}/${runId}`
|
|
176
|
+
}
|
|
177
|
+
export function statePath(runsRootDir: string, runId: string): string {
|
|
178
|
+
return `${runDir(runsRootDir, runId)}/state.json`
|
|
179
|
+
}
|
|
180
|
+
export function controlPath(runsRootDir: string, runId: string): string {
|
|
181
|
+
return `${runDir(runsRootDir, runId)}/control.json`
|
|
182
|
+
}
|
|
183
|
+
export function journalPath(runsRootDir: string, runId: string): string {
|
|
184
|
+
return `${runDir(runsRootDir, runId)}/journal.jsonl`
|
|
185
|
+
}
|
|
186
|
+
/** saved (named) workflow scripts: <worktree>/.opencode/workflows/<name>.js */
|
|
187
|
+
export function workflowRoot(worktree: string): string {
|
|
188
|
+
return `${worktree}/.opencode/workflows`
|
|
189
|
+
}
|