gm-orchestrator 0.2.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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +215 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/core/orchestrator.d.ts +23 -0
  8. package/dist/core/orchestrator.d.ts.map +1 -0
  9. package/dist/core/orchestrator.js +173 -0
  10. package/dist/core/orchestrator.js.map +1 -0
  11. package/dist/core/permissions.d.ts +18 -0
  12. package/dist/core/permissions.d.ts.map +1 -0
  13. package/dist/core/permissions.js +42 -0
  14. package/dist/core/permissions.js.map +1 -0
  15. package/dist/core/prompt-builder.d.ts +25 -0
  16. package/dist/core/prompt-builder.d.ts.map +1 -0
  17. package/dist/core/prompt-builder.js +84 -0
  18. package/dist/core/prompt-builder.js.map +1 -0
  19. package/dist/core/task-utils.d.ts +5 -0
  20. package/dist/core/task-utils.d.ts.map +1 -0
  21. package/dist/core/task-utils.js +27 -0
  22. package/dist/core/task-utils.js.map +1 -0
  23. package/dist/core/types.d.ts +150 -0
  24. package/dist/core/types.d.ts.map +1 -0
  25. package/dist/core/types.js +5 -0
  26. package/dist/core/types.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +12 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/infra/claude-runner.d.ts +14 -0
  32. package/dist/infra/claude-runner.d.ts.map +1 -0
  33. package/dist/infra/claude-runner.js +42 -0
  34. package/dist/infra/claude-runner.js.map +1 -0
  35. package/dist/infra/config.d.ts +7 -0
  36. package/dist/infra/config.d.ts.map +1 -0
  37. package/dist/infra/config.js +61 -0
  38. package/dist/infra/config.js.map +1 -0
  39. package/dist/infra/gm-client.d.ts +32 -0
  40. package/dist/infra/gm-client.d.ts.map +1 -0
  41. package/dist/infra/gm-client.js +70 -0
  42. package/dist/infra/gm-client.js.map +1 -0
  43. package/dist/infra/gm-discovery.d.ts +16 -0
  44. package/dist/infra/gm-discovery.d.ts.map +1 -0
  45. package/dist/infra/gm-discovery.js +53 -0
  46. package/dist/infra/gm-discovery.js.map +1 -0
  47. package/dist/infra/logger.d.ts +13 -0
  48. package/dist/infra/logger.d.ts.map +1 -0
  49. package/dist/infra/logger.js +44 -0
  50. package/dist/infra/logger.js.map +1 -0
  51. package/dist/infra/notifications/desktop.d.ts +6 -0
  52. package/dist/infra/notifications/desktop.d.ts.map +1 -0
  53. package/dist/infra/notifications/desktop.js +18 -0
  54. package/dist/infra/notifications/desktop.js.map +1 -0
  55. package/dist/infra/notifications/index.d.ts +9 -0
  56. package/dist/infra/notifications/index.d.ts.map +1 -0
  57. package/dist/infra/notifications/index.js +40 -0
  58. package/dist/infra/notifications/index.js.map +1 -0
  59. package/dist/infra/notifications/telegram.d.ts +9 -0
  60. package/dist/infra/notifications/telegram.d.ts.map +1 -0
  61. package/dist/infra/notifications/telegram.js +41 -0
  62. package/dist/infra/notifications/telegram.js.map +1 -0
  63. package/dist/infra/notifications/types.d.ts +30 -0
  64. package/dist/infra/notifications/types.d.ts.map +1 -0
  65. package/dist/infra/notifications/types.js +3 -0
  66. package/dist/infra/notifications/types.js.map +1 -0
  67. package/dist/infra/notifications/webhook.d.ts +9 -0
  68. package/dist/infra/notifications/webhook.d.ts.map +1 -0
  69. package/dist/infra/notifications/webhook.js +39 -0
  70. package/dist/infra/notifications/webhook.js.map +1 -0
  71. package/dist/infra/task-poller.d.ts +9 -0
  72. package/dist/infra/task-poller.d.ts.map +1 -0
  73. package/dist/infra/task-poller.js +42 -0
  74. package/dist/infra/task-poller.js.map +1 -0
  75. package/dist/server/api.d.ts +23 -0
  76. package/dist/server/api.d.ts.map +1 -0
  77. package/dist/server/api.js +143 -0
  78. package/dist/server/api.js.map +1 -0
  79. package/dist/server/index.d.ts +18 -0
  80. package/dist/server/index.d.ts.map +1 -0
  81. package/dist/server/index.js +73 -0
  82. package/dist/server/index.js.map +1 -0
  83. package/dist/server/runner-service.d.ts +15 -0
  84. package/dist/server/runner-service.d.ts.map +1 -0
  85. package/dist/server/runner-service.js +193 -0
  86. package/dist/server/runner-service.js.map +1 -0
  87. package/dist/server/ws.d.ts +9 -0
  88. package/dist/server/ws.d.ts.map +1 -0
  89. package/dist/server/ws.js +30 -0
  90. package/dist/server/ws.js.map +1 -0
  91. package/dist/ui/assets/index-BYPkFAEX.css +1 -0
  92. package/dist/ui/assets/index-CEQTkIqE.js +60 -0
  93. package/dist/ui/index.html +13 -0
  94. package/package.json +70 -0
@@ -0,0 +1,39 @@
1
+ export class WebhookNotifier {
2
+ url;
3
+ headers;
4
+ constructor(config) {
5
+ this.url = config.url;
6
+ this.headers = {
7
+ 'Content-Type': 'application/json',
8
+ ...config.headers,
9
+ };
10
+ }
11
+ async send(payload) {
12
+ const attempt = async () => {
13
+ const res = await fetch(this.url, {
14
+ method: 'POST',
15
+ headers: this.headers,
16
+ body: JSON.stringify(payload),
17
+ });
18
+ if (!res.ok) {
19
+ const body = await res.text();
20
+ throw new Error(`Webhook error ${res.status}: ${body}`);
21
+ }
22
+ };
23
+ try {
24
+ await attempt();
25
+ }
26
+ catch {
27
+ // Retry once on failure
28
+ await attempt();
29
+ }
30
+ }
31
+ async test() {
32
+ await this.send({
33
+ event: 'test',
34
+ title: 'gm-orchestrator connected',
35
+ body: 'Webhook notifications are working.',
36
+ });
37
+ }
38
+ }
39
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/infra/notifications/webhook.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAe;IACT,GAAG,CAAS;IACZ,OAAO,CAAyB;IAEjD,YAAY,MAAqB;QAC/B,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG;YACb,cAAc,EAAE,kBAAkB;YAClC,GAAG,MAAM,CAAC,OAAO;SAClB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACrC,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;YACxC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC9B,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;YACxB,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,2BAA2B;YAClC,IAAI,EAAE,oCAAoC;SAC3C,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { TaskPollerPort, GraphMemoryPort } from '../core/types.js';
2
+ export declare class TaskPoller implements TaskPollerPort {
3
+ private readonly gm;
4
+ constructor(gm: GraphMemoryPort);
5
+ waitForCompletion(taskId: string, { timeoutMs }: {
6
+ timeoutMs: number;
7
+ }): Promise<'done' | 'cancelled' | 'timeout'>;
8
+ }
9
+ //# sourceMappingURL=task-poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-poller.d.ts","sourceRoot":"","sources":["../../src/infra/task-poller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIxE,qBAAa,UAAW,YAAW,cAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAE1C,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GACnC,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;CAgC7C"}
@@ -0,0 +1,42 @@
1
+ const POLL_INTERVAL_MS = 3_000;
2
+ export class TaskPoller {
3
+ gm;
4
+ constructor(gm) {
5
+ this.gm = gm;
6
+ }
7
+ async waitForCompletion(taskId, { timeoutMs }) {
8
+ const deadline = Date.now() + timeoutMs;
9
+ let tick = 0;
10
+ while (Date.now() < deadline) {
11
+ await sleep(POLL_INTERVAL_MS);
12
+ let task;
13
+ try {
14
+ task = await this.gm.getTask(taskId);
15
+ }
16
+ catch {
17
+ // Transient network error — keep polling
18
+ continue;
19
+ }
20
+ if (task.status === 'done') {
21
+ clearProgress();
22
+ return 'done';
23
+ }
24
+ if (task.status === 'cancelled') {
25
+ clearProgress();
26
+ return 'cancelled';
27
+ }
28
+ // Progress dots
29
+ tick = (tick + 1) % 4;
30
+ process.stdout.write(`\r ⏳ ${taskId} ${'·'.repeat(tick + 1)} `);
31
+ }
32
+ clearProgress();
33
+ return 'timeout';
34
+ }
35
+ }
36
+ function clearProgress() {
37
+ process.stdout.write('\r' + ' '.repeat(60) + '\r');
38
+ }
39
+ function sleep(ms) {
40
+ return new Promise((r) => setTimeout(r, ms));
41
+ }
42
+ //# sourceMappingURL=task-poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-poller.js","sourceRoot":"","sources":["../../src/infra/task-poller.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,MAAM,OAAO,UAAU;IACQ;IAA7B,YAA6B,EAAmB;QAAnB,OAAE,GAAF,EAAE,CAAiB;IAAG,CAAC;IAEpD,KAAK,CAAC,iBAAiB,CACrB,MAAc,EACd,EAAE,SAAS,EAAyB;QAEpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,IAAI,IAAI,GAAG,CAAC,CAAC;QAEb,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;YAE9B,IAAI,IAAI,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,yCAAyC;gBACzC,SAAS;YACX,CAAC;YAED,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC3B,aAAa,EAAE,CAAC;gBAChB,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAChC,aAAa,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,gBAAgB;YAChB,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACrE,CAAC;QAED,aAAa,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAED,SAAS,aAAa;IACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { Router } from 'express';
2
+ import type { OrchestratorConfig, GraphMemoryPort } from '../core/types.js';
3
+ import type { Logger } from '../infra/logger.js';
4
+ import type { GMServer } from '../infra/gm-discovery.js';
5
+ export interface RunnerService {
6
+ isRunning: boolean;
7
+ startSprint(projectId: string, tag?: string): Promise<void>;
8
+ startEpic(projectId: string, epicId: string): Promise<void>;
9
+ stop(): Promise<void>;
10
+ }
11
+ export interface ApiDeps {
12
+ config: OrchestratorConfig;
13
+ logger: Logger;
14
+ gmDiscovery: {
15
+ discoverServers(): Promise<GMServer[]>;
16
+ };
17
+ gmClient: GraphMemoryPort;
18
+ runner: RunnerService;
19
+ saveConfig: (config: Partial<OrchestratorConfig>) => void;
20
+ version?: string;
21
+ }
22
+ export declare function createApiRouter(deps: ApiDeps): Router;
23
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/server/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAKzD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAID,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QAAE,eAAe,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;KAAE,CAAC;IACxD,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CA0IrD"}
@@ -0,0 +1,143 @@
1
+ import { Router } from 'express';
2
+ // ─── Router factory ─────────────────────────────────────────────────────
3
+ export function createApiRouter(deps) {
4
+ const router = Router();
5
+ // GET /api/status
6
+ router.get('/api/status', (_req, res) => {
7
+ const { apiKey: _redacted, ...redactedConfig } = deps.config;
8
+ res.json({
9
+ version: deps.version ?? '2.0.0',
10
+ config: redactedConfig,
11
+ isRunning: deps.runner.isRunning,
12
+ });
13
+ });
14
+ // GET /api/projects
15
+ router.get('/api/projects', async (_req, res, next) => {
16
+ try {
17
+ const servers = await deps.gmDiscovery.discoverServers();
18
+ res.json({ servers });
19
+ }
20
+ catch (err) {
21
+ next(err);
22
+ }
23
+ });
24
+ // GET /api/projects/:id/tasks
25
+ router.get('/api/projects/:id/tasks', async (req, res, next) => {
26
+ try {
27
+ const opts = {};
28
+ if (req.query['tag'])
29
+ opts.tag = req.query['tag'];
30
+ if (req.query['status'])
31
+ opts.status = req.query['status'];
32
+ if (req.query['limit'])
33
+ opts.limit = Number(req.query['limit']);
34
+ const tasks = await deps.gmClient.listTasks(opts);
35
+ res.json({ tasks });
36
+ }
37
+ catch (err) {
38
+ next(err);
39
+ }
40
+ });
41
+ // GET /api/projects/:id/epics
42
+ router.get('/api/projects/:id/epics', async (req, res, next) => {
43
+ try {
44
+ const opts = {};
45
+ if (req.query['status'])
46
+ opts.status = req.query['status'];
47
+ if (req.query['limit'])
48
+ opts.limit = Number(req.query['limit']);
49
+ const epics = await deps.gmClient.listEpics(opts);
50
+ res.json({ epics });
51
+ }
52
+ catch (err) {
53
+ next(err);
54
+ }
55
+ });
56
+ // POST /api/run/sprint
57
+ router.post('/api/run/sprint', async (req, res, next) => {
58
+ try {
59
+ const { projectId, tag } = req.body;
60
+ if (!projectId) {
61
+ res.status(400).json({ error: 'projectId is required' });
62
+ return;
63
+ }
64
+ if (deps.runner.isRunning) {
65
+ res.status(409).json({ error: 'A run is already in progress' });
66
+ return;
67
+ }
68
+ // Fire and forget — progress is streamed via WebSocket
69
+ deps.runner.startSprint(projectId, tag).catch((err) => {
70
+ deps.logger.error(`Sprint run failed: ${err.message}`);
71
+ });
72
+ res.json({ ok: true, mode: 'sprint', projectId, tag });
73
+ }
74
+ catch (err) {
75
+ next(err);
76
+ }
77
+ });
78
+ // POST /api/run/epic
79
+ router.post('/api/run/epic', async (req, res, next) => {
80
+ try {
81
+ const { projectId, epicId } = req.body;
82
+ if (!projectId || !epicId) {
83
+ res.status(400).json({ error: 'projectId and epicId are required' });
84
+ return;
85
+ }
86
+ if (deps.runner.isRunning) {
87
+ res.status(409).json({ error: 'A run is already in progress' });
88
+ return;
89
+ }
90
+ deps.runner.startEpic(projectId, epicId).catch((err) => {
91
+ deps.logger.error(`Epic run failed: ${err.message}`);
92
+ });
93
+ res.json({ ok: true, mode: 'epic', projectId, epicId });
94
+ }
95
+ catch (err) {
96
+ next(err);
97
+ }
98
+ });
99
+ // POST /api/run/stop
100
+ router.post('/api/run/stop', async (_req, res, next) => {
101
+ try {
102
+ if (!deps.runner.isRunning) {
103
+ res.status(409).json({ error: 'No run is in progress' });
104
+ return;
105
+ }
106
+ await deps.runner.stop();
107
+ res.json({ ok: true });
108
+ }
109
+ catch (err) {
110
+ next(err);
111
+ }
112
+ });
113
+ // GET /api/config
114
+ router.get('/api/config', (_req, res) => {
115
+ const { apiKey: _redacted, ...redactedConfig } = deps.config;
116
+ res.json(redactedConfig);
117
+ });
118
+ // PUT /api/config
119
+ router.put('/api/config', (req, res, next) => {
120
+ try {
121
+ const body = req.body;
122
+ if (!body || typeof body !== 'object') {
123
+ res.status(400).json({ error: 'Request body must be a JSON object' });
124
+ return;
125
+ }
126
+ // Merge into current config
127
+ Object.assign(deps.config, body);
128
+ deps.saveConfig(deps.config);
129
+ const { apiKey: _redacted, ...redactedConfig } = deps.config;
130
+ res.json(redactedConfig);
131
+ }
132
+ catch (err) {
133
+ next(err);
134
+ }
135
+ });
136
+ // ── Error handling middleware ────────────────────────────────────────
137
+ router.use('/api', (err, _req, res, _next) => {
138
+ deps.logger.error(`API error: ${err.message}`);
139
+ res.status(500).json({ error: err.message });
140
+ });
141
+ return router;
142
+ }
143
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/server/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import express from 'express';
2
+ import type { Server } from 'http';
3
+ import type { Logger } from '../infra/logger.js';
4
+ export interface ServerDeps {
5
+ logger: Logger;
6
+ port?: number;
7
+ }
8
+ export declare function createServer(deps: ServerDeps): {
9
+ app: express.Express;
10
+ start: () => Promise<Server>;
11
+ stop: (server: Server) => Promise<void>;
12
+ mountStaticUI: () => void;
13
+ };
14
+ export declare function startServer(deps: ServerDeps): Promise<{
15
+ server: Server;
16
+ stop: () => Promise<void>;
17
+ }>;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAaD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,IAAI,CAAA;CAAE,CAiDzK;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAe1G"}
@@ -0,0 +1,73 @@
1
+ import { existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import express from 'express';
4
+ const FALLBACK_HTML = `<!DOCTYPE html>
5
+ <html lang="en">
6
+ <head><meta charset="utf-8"><title>gm-orchestrator</title></head>
7
+ <body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1a1a2e;color:#e0e0e0">
8
+ <div style="text-align:center">
9
+ <h1>UI not built</h1>
10
+ <p>Run <code style="background:#2d2d44;padding:4px 8px;border-radius:4px">npm run build:ui</code> first.</p>
11
+ </div>
12
+ </body>
13
+ </html>`;
14
+ export function createServer(deps) {
15
+ const { logger } = deps;
16
+ const port = deps.port ?? (Number(process.env['GM_PORT']) || 4242);
17
+ const app = express();
18
+ app.use(express.json());
19
+ // Call after mounting API routes so the catch-all doesn't shadow them
20
+ function mountStaticUI() {
21
+ const uiDir = resolve(process.cwd(), 'dist', 'ui');
22
+ if (existsSync(uiDir)) {
23
+ app.use(express.static(uiDir));
24
+ // SPA fallback — serve index.html for unmatched routes
25
+ app.get('{*path}', (_req, res) => {
26
+ res.sendFile(resolve(uiDir, 'index.html'));
27
+ });
28
+ }
29
+ else {
30
+ app.get('{*path}', (_req, res) => {
31
+ res.type('html').send(FALLBACK_HTML);
32
+ });
33
+ }
34
+ }
35
+ async function start() {
36
+ return new Promise((resolvePromise) => {
37
+ const server = app.listen(port, () => {
38
+ logger.info(`Server listening on http://localhost:${port}`);
39
+ // Open browser (fire-and-forget, don't block on import failure)
40
+ import('open').then((mod) => mod.default(`http://localhost:${port}`)).catch(() => { });
41
+ resolvePromise(server);
42
+ });
43
+ });
44
+ }
45
+ async function stop(server) {
46
+ return new Promise((resolvePromise, reject) => {
47
+ server.close((err) => {
48
+ if (err) {
49
+ reject(err);
50
+ }
51
+ else {
52
+ logger.info('Server stopped');
53
+ resolvePromise();
54
+ }
55
+ });
56
+ });
57
+ }
58
+ return { app, start, stop, mountStaticUI };
59
+ }
60
+ export async function startServer(deps) {
61
+ const { start, stop, mountStaticUI } = createServer(deps);
62
+ mountStaticUI();
63
+ const server = await start();
64
+ const shutdown = async () => {
65
+ deps.logger.info('Shutting down...');
66
+ await stop(server);
67
+ process.exit(0);
68
+ };
69
+ process.on('SIGINT', shutdown);
70
+ process.on('SIGTERM', shutdown);
71
+ return { server, stop: () => stop(server) };
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { OrchestratorConfig, GraphMemoryPort, ClaudeRunnerPort, TaskPollerPort } from '../core/types.js';
2
+ import type { Logger } from '../infra/logger.js';
3
+ import type { WebSocketBus } from './ws.js';
4
+ import type { RunnerService } from './api.js';
5
+ export type RunState = 'idle' | 'running' | 'stopping';
6
+ export interface RunnerServiceDeps {
7
+ config: OrchestratorConfig;
8
+ gm: GraphMemoryPort;
9
+ runner: ClaudeRunnerPort;
10
+ poller: TaskPollerPort;
11
+ logger: Logger;
12
+ wsBus: WebSocketBus;
13
+ }
14
+ export declare function createRunnerService(deps: RunnerServiceDeps): RunnerService;
15
+ //# sourceMappingURL=runner-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-service.d.ts","sourceRoot":"","sources":["../../src/server/runner-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE,eAAe,CAAC;IACpB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,CAAC;CACrB;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAqN1E"}
@@ -0,0 +1,193 @@
1
+ import { spawn } from 'child_process';
2
+ import { buildPrompt } from '../core/prompt-builder.js';
3
+ import { runSprint, runEpic } from '../core/orchestrator.js';
4
+ // ─── Implementation ──────────────────────────────────────────────────────
5
+ export function createRunnerService(deps) {
6
+ let state = 'idle';
7
+ let activeAbort = null;
8
+ let activeTaskId = null;
9
+ let runPromise = null;
10
+ const { logger, wsBus } = deps;
11
+ function emit(event) {
12
+ wsBus.broadcast(event);
13
+ }
14
+ // Wrap the orchestrator logger to intercept events and forward to WS
15
+ function createWsLogger() {
16
+ return {
17
+ info: (msg) => logger.info(msg),
18
+ success: (msg) => logger.success(msg),
19
+ warn: (msg) => logger.warn(msg),
20
+ error: (msg) => {
21
+ logger.error(msg);
22
+ emit({ type: 'error', payload: { message: msg } });
23
+ },
24
+ skip: (msg) => logger.skip(msg),
25
+ section: (msg) => logger.section(msg),
26
+ task: (task) => {
27
+ logger.task(task);
28
+ activeTaskId = task.id;
29
+ emit({ type: 'task:started', payload: { task } });
30
+ },
31
+ };
32
+ }
33
+ // Wraps the injected runner to intercept stdout and emit log:line events.
34
+ // In dry-run mode the orchestrator skips runner.run(), so this is safe.
35
+ function createStreamingRunner() {
36
+ return {
37
+ async run(task, config) {
38
+ const prompt = buildPrompt(task, { projectId: config.projectId });
39
+ const args = ['--print', '--dangerously-skip-permissions', ...config.claudeArgs, prompt];
40
+ return new Promise((resolve, reject) => {
41
+ const proc = spawn('claude', args, {
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ env: process.env,
44
+ });
45
+ // Stream stdout line by line
46
+ if (proc.stdout) {
47
+ let buffer = '';
48
+ proc.stdout.on('data', (chunk) => {
49
+ buffer += chunk.toString();
50
+ const lines = buffer.split('\n');
51
+ buffer = lines.pop() ?? '';
52
+ for (const line of lines) {
53
+ emit({ type: 'log:line', payload: { taskId: task.id, line } });
54
+ }
55
+ });
56
+ proc.stdout.on('end', () => {
57
+ if (buffer) {
58
+ emit({ type: 'log:line', payload: { taskId: task.id, line: buffer } });
59
+ }
60
+ });
61
+ }
62
+ // Also stream stderr
63
+ if (proc.stderr) {
64
+ let buffer = '';
65
+ proc.stderr.on('data', (chunk) => {
66
+ buffer += chunk.toString();
67
+ const lines = buffer.split('\n');
68
+ buffer = lines.pop() ?? '';
69
+ for (const line of lines) {
70
+ emit({ type: 'log:line', payload: { taskId: task.id, line: `[stderr] ${line}` } });
71
+ }
72
+ });
73
+ }
74
+ // Handle abort
75
+ if (activeAbort) {
76
+ activeAbort.signal.addEventListener('abort', () => {
77
+ proc.kill('SIGTERM');
78
+ });
79
+ }
80
+ proc.on('close', () => resolve());
81
+ proc.on('error', (err) => {
82
+ if (err.code === 'ENOENT') {
83
+ reject(new Error('claude not found in PATH'));
84
+ }
85
+ else {
86
+ reject(err);
87
+ }
88
+ });
89
+ });
90
+ },
91
+ };
92
+ }
93
+ async function startSprint(projectId, tag) {
94
+ if (state !== 'idle') {
95
+ throw new Error('A run is already in progress');
96
+ }
97
+ state = 'running';
98
+ activeAbort = new AbortController();
99
+ const config = {
100
+ ...deps.config,
101
+ projectId,
102
+ ...(tag !== undefined ? { tag } : {}),
103
+ };
104
+ emit({ type: 'run:started', payload: { mode: 'sprint' } });
105
+ logger.section(`Runner: starting sprint (project=${projectId}${tag ? `, tag=${tag}` : ''})`);
106
+ const runner = config.dryRun ? deps.runner : createStreamingRunner();
107
+ runPromise = runSprint({
108
+ gm: deps.gm,
109
+ runner,
110
+ poller: deps.poller,
111
+ logger: createWsLogger(),
112
+ }, config);
113
+ try {
114
+ const stats = await runPromise;
115
+ if (state !== 'stopping') {
116
+ emit({ type: 'run:complete', payload: stats });
117
+ }
118
+ }
119
+ catch (err) {
120
+ emit({ type: 'error', payload: { message: err.message } });
121
+ throw err;
122
+ }
123
+ finally {
124
+ state = 'idle';
125
+ activeAbort = null;
126
+ activeTaskId = null;
127
+ runPromise = null;
128
+ }
129
+ }
130
+ async function startEpic(projectId, epicId) {
131
+ if (state !== 'idle') {
132
+ throw new Error('A run is already in progress');
133
+ }
134
+ state = 'running';
135
+ activeAbort = new AbortController();
136
+ const config = { ...deps.config, projectId };
137
+ emit({ type: 'run:started', payload: { mode: 'epic', epicId } });
138
+ logger.section(`Runner: starting epic ${epicId} (project=${projectId})`);
139
+ const runner = config.dryRun ? deps.runner : createStreamingRunner();
140
+ runPromise = runEpic(epicId, {
141
+ gm: deps.gm,
142
+ runner,
143
+ poller: deps.poller,
144
+ logger: createWsLogger(),
145
+ }, config);
146
+ try {
147
+ const stats = await runPromise;
148
+ if (state !== 'stopping') {
149
+ emit({ type: 'run:complete', payload: stats });
150
+ }
151
+ }
152
+ catch (err) {
153
+ emit({ type: 'error', payload: { message: err.message } });
154
+ throw err;
155
+ }
156
+ finally {
157
+ state = 'idle';
158
+ activeAbort = null;
159
+ activeTaskId = null;
160
+ runPromise = null;
161
+ }
162
+ }
163
+ async function stop() {
164
+ if (state !== 'running') {
165
+ return;
166
+ }
167
+ state = 'stopping';
168
+ logger.warn('Runner: stop requested');
169
+ if (activeAbort) {
170
+ activeAbort.abort();
171
+ }
172
+ // Wait for the current run to wind down
173
+ if (runPromise) {
174
+ try {
175
+ await runPromise;
176
+ }
177
+ catch {
178
+ // Expected — run may throw when aborted
179
+ }
180
+ }
181
+ emit({ type: 'run:stopped' });
182
+ logger.info('Runner: stopped');
183
+ }
184
+ return {
185
+ get isRunning() {
186
+ return state !== 'idle';
187
+ },
188
+ startSprint,
189
+ startEpic,
190
+ stop,
191
+ };
192
+ }
193
+ //# sourceMappingURL=runner-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-service.js","sourceRoot":"","sources":["../../src/server/runner-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Server } from 'http';
2
+ import type { ServerEvent } from '../core/types.js';
3
+ export interface WebSocketBus {
4
+ broadcast(event: ServerEvent): void;
5
+ readonly clientCount: number;
6
+ close(): Promise<void>;
7
+ }
8
+ export declare function createWebSocketServer(httpServer: Server): WebSocketBus;
9
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/server/ws.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CA4BtE"}