better-symphony 1.0.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.
Files changed (63) hide show
  1. package/CLAUDE.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +292 -0
  4. package/dist/web/app.css +2 -0
  5. package/dist/web/index.html +13 -0
  6. package/dist/web/main.js +235 -0
  7. package/package.json +62 -0
  8. package/src/agent/claude-runner.ts +576 -0
  9. package/src/agent/protocol.ts +2 -0
  10. package/src/agent/runner.ts +2 -0
  11. package/src/agent/session.ts +113 -0
  12. package/src/cli.ts +354 -0
  13. package/src/config/loader.ts +379 -0
  14. package/src/config/types.ts +382 -0
  15. package/src/index.ts +53 -0
  16. package/src/linear-cli.ts +414 -0
  17. package/src/logging/logger.ts +143 -0
  18. package/src/orchestrator/multi-orchestrator.ts +266 -0
  19. package/src/orchestrator/orchestrator.ts +1357 -0
  20. package/src/orchestrator/scheduler.ts +195 -0
  21. package/src/orchestrator/state.ts +201 -0
  22. package/src/prompts/github-system-prompt.md +51 -0
  23. package/src/prompts/linear-system-prompt.md +44 -0
  24. package/src/tracker/client.ts +577 -0
  25. package/src/tracker/github-issues-tracker.ts +280 -0
  26. package/src/tracker/github-pr-tracker.ts +298 -0
  27. package/src/tracker/index.ts +9 -0
  28. package/src/tracker/interface.ts +76 -0
  29. package/src/tracker/linear-tracker.ts +147 -0
  30. package/src/tracker/queries.ts +281 -0
  31. package/src/tracker/types.ts +125 -0
  32. package/src/tui/App.tsx +157 -0
  33. package/src/tui/LogView.tsx +120 -0
  34. package/src/tui/StatusBar.tsx +72 -0
  35. package/src/tui/TabBar.tsx +55 -0
  36. package/src/tui/sink.ts +47 -0
  37. package/src/tui/types.ts +6 -0
  38. package/src/tui/useOrchestrator.ts +244 -0
  39. package/src/web/server.ts +182 -0
  40. package/src/web/sink.ts +67 -0
  41. package/src/web-ui/App.tsx +60 -0
  42. package/src/web-ui/components/agent-table.tsx +57 -0
  43. package/src/web-ui/components/header.tsx +72 -0
  44. package/src/web-ui/components/log-stream.tsx +111 -0
  45. package/src/web-ui/components/retry-table.tsx +58 -0
  46. package/src/web-ui/components/stats-cards.tsx +142 -0
  47. package/src/web-ui/components/ui/badge.tsx +30 -0
  48. package/src/web-ui/components/ui/button.tsx +39 -0
  49. package/src/web-ui/components/ui/card.tsx +32 -0
  50. package/src/web-ui/globals.css +27 -0
  51. package/src/web-ui/index.html +13 -0
  52. package/src/web-ui/lib/use-sse.ts +98 -0
  53. package/src/web-ui/lib/utils.ts +25 -0
  54. package/src/web-ui/main.tsx +4 -0
  55. package/src/workspace/hooks.ts +97 -0
  56. package/src/workspace/manager.ts +211 -0
  57. package/src/workspace/render-hook.ts +13 -0
  58. package/workflows/dev.md +127 -0
  59. package/workflows/github-issues.md +107 -0
  60. package/workflows/pr-review.md +89 -0
  61. package/workflows/prd.md +170 -0
  62. package/workflows/ralph.md +95 -0
  63. package/workflows/smoke.md +66 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Symphony Configuration Types
3
+ */
4
+
5
+ // ── Issue Domain Model ──────────────────────────────────────────
6
+
7
+ export interface BlockerRef {
8
+ id: string | null;
9
+ identifier: string | null;
10
+ state: string | null;
11
+ }
12
+
13
+ export interface ChildIssue {
14
+ id: string;
15
+ identifier: string;
16
+ title: string;
17
+ description: string | null;
18
+ priority: number | null;
19
+ state: string;
20
+ state_type: string; // "triage" | "backlog" | "unstarted" | "started" | "completed" | "canceled"
21
+ sort_order: number;
22
+ assignee: string | null;
23
+ created_at: Date | null;
24
+ updated_at: Date | null;
25
+ }
26
+
27
+ export interface Comment {
28
+ id: string;
29
+ body: string;
30
+ user: string | null;
31
+ created_at: Date | null;
32
+ }
33
+
34
+ export interface Issue {
35
+ id: string;
36
+ identifier: string;
37
+ title: string;
38
+ description: string | null;
39
+ priority: number | null;
40
+ state: string;
41
+ branch_name: string | null;
42
+ url: string | null;
43
+ labels: string[];
44
+ blocked_by: BlockerRef[];
45
+ children: ChildIssue[];
46
+ comments: Comment[];
47
+ created_at: Date | null;
48
+ updated_at: Date | null;
49
+ // GitHub PR specific (optional)
50
+ base_branch?: string;
51
+ author?: string;
52
+ files_changed?: number;
53
+ number?: number;
54
+ }
55
+
56
+ // ── Workflow Definition ─────────────────────────────────────────
57
+
58
+ export interface WorkflowDefinition {
59
+ config: WorkflowConfig;
60
+ prompt_template: string;
61
+ }
62
+
63
+ export interface WorkflowConfig {
64
+ tracker?: TrackerConfig;
65
+ polling?: PollingConfig;
66
+ workspace?: WorkspaceConfig;
67
+ hooks?: HooksConfig;
68
+ agent?: AgentConfig;
69
+ }
70
+
71
+ export interface TrackerConfig {
72
+ kind: "linear" | "github-pr" | "github-issues";
73
+ // Linear-specific
74
+ endpoint?: string;
75
+ api_key?: string;
76
+ project_slug?: string;
77
+ active_states?: string[] | string;
78
+ terminal_states?: string[] | string;
79
+ error_states?: string[] | string;
80
+ // GitHub-specific
81
+ repo?: string;
82
+ // Shared
83
+ /** Labels that must be present on an issue for it to be picked up */
84
+ required_labels?: string[] | string;
85
+ /** Labels that exclude an issue from being picked up */
86
+ excluded_labels?: string[] | string;
87
+ }
88
+
89
+ export interface PollingConfig {
90
+ interval_ms?: number | string;
91
+ }
92
+
93
+ export interface WorkspaceConfig {
94
+ root?: string;
95
+ }
96
+
97
+ export interface HooksConfig {
98
+ after_create?: string;
99
+ before_run?: string;
100
+ after_run?: string;
101
+ before_remove?: string;
102
+ timeout_ms?: number | string;
103
+ }
104
+
105
+ export type AgentBinary = "claude" | "codex" | "opencode";
106
+ /** @deprecated Use AgentBinary instead */
107
+ export type AgentHarness = AgentBinary;
108
+
109
+ export interface AgentConfig {
110
+ /** Which CLI binary to use (default: "claude"). Can also be a path. */
111
+ binary?: string;
112
+ /** @deprecated Use `binary` instead */
113
+ harness?: AgentBinary;
114
+ /** Agent mode: "default" or "ralph_loop" (external subtask orchestration) */
115
+ mode?: "default" | "ralph_loop";
116
+ max_concurrent_agents?: number | string;
117
+ max_turns?: number | string;
118
+ max_retries?: number | string;
119
+ max_retry_backoff_ms?: number | string;
120
+ max_concurrent_agents_by_state?: Record<string, number | string>;
121
+ turn_timeout_ms?: number | string;
122
+ stall_timeout_ms?: number | string;
123
+ /** Ralph loop: max subtasks per run (default: unlimited) */
124
+ max_iterations?: number | string;
125
+ /** Run the agent binary inside yolobox */
126
+ yolobox?: boolean;
127
+ /** Extra arguments passed to yolobox (before the -- separator) */
128
+ yolobox_arguments?: string[];
129
+ /** Permission mode (default: "acceptEdits") */
130
+ permission_mode?: string;
131
+ /** Append to system prompt */
132
+ append_system_prompt?: string;
133
+ }
134
+
135
+ // ── Service Config (Typed View) ─────────────────────────────────
136
+
137
+ export interface ServiceConfig {
138
+ tracker: {
139
+ kind: "linear" | "github-pr" | "github-issues";
140
+ // Linear-specific (required for linear, empty string for github-pr)
141
+ endpoint: string;
142
+ api_key: string;
143
+ project_slug: string;
144
+ active_states: string[];
145
+ terminal_states: string[];
146
+ error_states: string[];
147
+ // GitHub-specific
148
+ repo: string;
149
+ // Shared
150
+ required_labels: string[];
151
+ excluded_labels: string[];
152
+ };
153
+ polling: {
154
+ interval_ms: number;
155
+ };
156
+ workspace: {
157
+ root: string;
158
+ };
159
+ hooks: {
160
+ after_create: string | null;
161
+ before_run: string | null;
162
+ after_run: string | null;
163
+ before_remove: string | null;
164
+ timeout_ms: number;
165
+ };
166
+ agent: {
167
+ binary: AgentBinary;
168
+ mode: "default" | "ralph_loop";
169
+ max_concurrent_agents: number;
170
+ max_turns: number;
171
+ max_retries: number;
172
+ max_retry_backoff_ms: number;
173
+ max_concurrent_agents_by_state: Map<string, number>;
174
+ turn_timeout_ms: number;
175
+ stall_timeout_ms: number;
176
+ max_iterations: number;
177
+ yolobox: boolean;
178
+ yolobox_arguments: string[];
179
+ permission_mode: string;
180
+ append_system_prompt: string | null;
181
+ };
182
+ }
183
+
184
+ // ── Workspace ───────────────────────────────────────────────────
185
+
186
+ export interface Workspace {
187
+ path: string;
188
+ workspace_key: string;
189
+ created_now: boolean;
190
+ }
191
+
192
+ // ── Run Attempt ─────────────────────────────────────────────────
193
+
194
+ export type RunAttemptStatus =
195
+ | "PreparingWorkspace"
196
+ | "BuildingPrompt"
197
+ | "LaunchingAgentProcess"
198
+ | "InitializingSession"
199
+ | "StreamingTurn"
200
+ | "Finishing"
201
+ | "Succeeded"
202
+ | "Failed"
203
+ | "TimedOut"
204
+ | "Stalled"
205
+ | "CanceledByReconciliation";
206
+
207
+ export interface RunAttempt {
208
+ issue_id: string;
209
+ issue_identifier: string;
210
+ attempt: number | null;
211
+ workspace_path: string;
212
+ started_at: Date;
213
+ status: RunAttemptStatus;
214
+ error?: string;
215
+ }
216
+
217
+ // ── Live Session ────────────────────────────────────────────────
218
+
219
+ export interface LiveSession {
220
+ session_id: string;
221
+ thread_id: string;
222
+ turn_id: string;
223
+ process_pid: string | null;
224
+ last_event: string | null;
225
+ last_activity_at: Date | null;
226
+ last_message: string | null;
227
+ input_tokens: number;
228
+ output_tokens: number;
229
+ total_tokens: number;
230
+ last_reported_input_tokens: number;
231
+ last_reported_output_tokens: number;
232
+ last_reported_total_tokens: number;
233
+ turn_count: number;
234
+ cost_usd: number;
235
+ duration_ms: number;
236
+ }
237
+
238
+ // ── Retry Entry ─────────────────────────────────────────────────
239
+
240
+ export interface RetryEntry {
241
+ issue_id: string;
242
+ identifier: string;
243
+ attempt: number;
244
+ due_at_ms: number;
245
+ timer_handle: Timer;
246
+ error: string | null;
247
+ }
248
+
249
+ // ── Running Entry ───────────────────────────────────────────────
250
+
251
+ export interface RunningEntry {
252
+ issue: Issue;
253
+ attempt: RunAttempt;
254
+ session: LiveSession | null;
255
+ worker: Promise<void>;
256
+ abortController: AbortController;
257
+ }
258
+
259
+ // ── Token/Rate Limit Tracking ───────────────────────────────────
260
+
261
+ export interface TokenTotals {
262
+ input_tokens: number;
263
+ output_tokens: number;
264
+ total_tokens: number;
265
+ seconds_running: number;
266
+ }
267
+
268
+ export interface RateLimitInfo {
269
+ requests_limit?: number;
270
+ requests_remaining?: number;
271
+ requests_reset?: number;
272
+ tokens_limit?: number;
273
+ tokens_remaining?: number;
274
+ tokens_reset?: number;
275
+ }
276
+
277
+ // ── Orchestrator Runtime State ──────────────────────────────────
278
+
279
+ export interface OrchestratorState {
280
+ poll_interval_ms: number;
281
+ max_concurrent_agents: number;
282
+ running: Map<string, RunningEntry>;
283
+ claimed: Set<string>;
284
+ retry_attempts: Map<string, RetryEntry>;
285
+ completed: Set<string>;
286
+ token_totals: TokenTotals;
287
+ rate_limits: RateLimitInfo | null;
288
+ ended_seconds: number;
289
+ }
290
+
291
+ // ── Agent Events ────────────────────────────────────────────────
292
+
293
+ export type AgentEventType =
294
+ | "session_started"
295
+ | "startup_failed"
296
+ | "turn_completed"
297
+ | "turn_failed"
298
+ | "turn_cancelled"
299
+ | "turn_ended_with_error"
300
+ | "turn_input_required"
301
+ | "tool_use"
302
+ | "tool_result"
303
+ | "assistant_message"
304
+ | "notification"
305
+ | "other_message"
306
+ | "malformed"
307
+ | "token_usage_updated";
308
+
309
+ export interface AgentEvent {
310
+ event: AgentEventType;
311
+ timestamp: Date;
312
+ process_pid?: string;
313
+ usage?: {
314
+ input_tokens?: number;
315
+ output_tokens?: number;
316
+ total_tokens?: number;
317
+ };
318
+ cost_usd?: number;
319
+ duration_ms?: number;
320
+ payload?: unknown;
321
+ message?: string;
322
+ }
323
+
324
+ // ── Errors ──────────────────────────────────────────────────────
325
+
326
+ export type WorkflowErrorClass =
327
+ | "missing_workflow_file"
328
+ | "workflow_parse_error"
329
+ | "workflow_front_matter_not_a_map"
330
+ | "template_parse_error"
331
+ | "template_render_error";
332
+
333
+ export class WorkflowError extends Error {
334
+ constructor(
335
+ public readonly errorClass: WorkflowErrorClass,
336
+ message: string
337
+ ) {
338
+ super(message);
339
+ this.name = "WorkflowError";
340
+ }
341
+ }
342
+
343
+ export type TrackerErrorClass =
344
+ | "unsupported_tracker_kind"
345
+ | "missing_tracker_api_key"
346
+ | "missing_tracker_project_slug"
347
+ | "linear_api_request"
348
+ | "linear_api_status"
349
+ | "linear_graphql_errors"
350
+ | "linear_unknown_payload"
351
+ | "linear_missing_end_cursor";
352
+
353
+ export class TrackerError extends Error {
354
+ constructor(
355
+ public readonly errorClass: TrackerErrorClass,
356
+ message: string
357
+ ) {
358
+ super(message);
359
+ this.name = "TrackerError";
360
+ }
361
+ }
362
+
363
+ export type AgentErrorClass =
364
+ | "agent_not_found"
365
+ | "invalid_workspace_cwd"
366
+ | "response_timeout"
367
+ | "turn_timeout"
368
+ | "process_exit"
369
+ | "response_error"
370
+ | "turn_failed"
371
+ | "turn_cancelled"
372
+ | "turn_input_required";
373
+
374
+ export class AgentError extends Error {
375
+ constructor(
376
+ public readonly errorClass: AgentErrorClass,
377
+ message: string
378
+ ) {
379
+ super(message);
380
+ this.name = "AgentError";
381
+ }
382
+ }
package/src/index.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Symphony - Coding Agent Orchestrator
3
+ */
4
+
5
+ // Config
6
+ export * from "./config/types.js";
7
+ export * from "./config/loader.js";
8
+
9
+ // Tracker
10
+ export { LinearClient } from "./tracker/client.js";
11
+ export * from "./tracker/types.js";
12
+
13
+ // Workspace
14
+ export { WorkspaceManager, sanitizeWorkspaceKey, validateWorkspacePath } from "./workspace/manager.js";
15
+ export { executeHook } from "./workspace/hooks.js";
16
+
17
+ // Agent
18
+ export { ClaudeRunner } from "./agent/claude-runner.js";
19
+ export {
20
+ createSession,
21
+ updateSessionTurnId,
22
+ updateSessionEvent,
23
+ updateSessionTokens,
24
+ createEmptyTotals,
25
+ parseRateLimits,
26
+ } from "./agent/session.js";
27
+
28
+ // Orchestrator
29
+ export { Orchestrator } from "./orchestrator/orchestrator.js";
30
+ export { MultiOrchestrator } from "./orchestrator/multi-orchestrator.js";
31
+ export {
32
+ createOrchestratorState,
33
+ claimIssue,
34
+ releaseClaim,
35
+ isIssueClaimed,
36
+ addRunning,
37
+ removeRunning,
38
+ getRunning,
39
+ isIssueRunning,
40
+ getRunningCount,
41
+ getRunningByState,
42
+ addRetry,
43
+ removeRetry,
44
+ getRetry,
45
+ updateRateLimits,
46
+ createSnapshot,
47
+ type RuntimeSnapshot,
48
+ } from "./orchestrator/state.js";
49
+ export * from "./orchestrator/scheduler.js";
50
+
51
+ // Logging
52
+ export { logger, createConsoleSink, createFileSink } from "./logging/logger.js";
53
+ export type { LogLevel, LogContext, LogEntry, LogSink } from "./logging/logger.js";