speexor 0.1.1 → 0.2.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.
- package/API-REFERENCE.md +96 -1
- package/ARCHITECTURE.md +83 -32
- package/BENCHMARKS.md +73 -0
- package/CHANGELOG.md +59 -4
- package/CODE-OF-CONDUCT.md +83 -83
- package/CONTRIBUTING.md +92 -97
- package/FAQ.md +132 -105
- package/GLOSSARY.md +34 -0
- package/LICENSE.md +21 -21
- package/PUBLISH.md +82 -77
- package/README.md +220 -6
- package/REFACTOR-LOG.md +40 -40
- package/ROADMAP.md +31 -42
- package/SECURITY-DEFAULTS.md +118 -0
- package/SECURITY.md +80 -79
- package/SUMMARY.md +31 -8
- package/TESTING.md +140 -140
- package/dist/{agent-5D3BVWNK.js → agent-C64T66XT.js} +4 -4
- package/dist/agent-C64T66XT.js.map +1 -0
- package/dist/{chunk-B7WLHC4W.js → chunk-5OD5UWB5.js} +322 -121
- package/dist/chunk-5OD5UWB5.js.map +1 -0
- package/dist/chunk-GOGI3JQD.js +1637 -0
- package/dist/chunk-GOGI3JQD.js.map +1 -0
- package/dist/{chunk-2F66BZYJ.js → chunk-VEZQT5SX.js} +80 -8
- package/dist/chunk-VEZQT5SX.js.map +1 -0
- package/dist/cli/index.js +2058 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +682 -3
- package/dist/core/index.js +1 -1
- package/dist/index.d.ts +102 -14
- package/dist/index.js +55 -29
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/types-BOMap-tI.d.ts +389 -0
- package/docs/PRD03.md +119 -0
- package/docs/PRD06.md +125 -0
- package/docs/SETUP.md +94 -94
- package/docs/TROUBLESHOOTING.md +113 -113
- package/docs/adr/0001-record-architecture-decisions.md +44 -0
- package/docs/adr/0002-plugin-architecture.md +53 -0
- package/docs/adr/0003-recursive-task-decomposition.md +57 -0
- package/docs/adr/0004-local-first-security.md +58 -0
- package/docs/adr/0005-data-directory-layout.md +69 -0
- package/examples/basic.yaml +61 -61
- package/package.json +103 -102
- package/schema/config.schema.json +119 -119
- package/speexor.config.yaml.example +30 -30
- package/dist/agent-5D3BVWNK.js.map +0 -1
- package/dist/chunk-2F66BZYJ.js.map +0 -1
- package/dist/chunk-B7WLHC4W.js.map +0 -1
- package/dist/chunk-SXALZEOJ.js +0 -345
- package/dist/chunk-SXALZEOJ.js.map +0 -1
- package/dist/types-0q_okI2g.d.ts +0 -205
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../chunk-5NA2TFPG.js';
|
|
2
|
-
export { DEFAULT_REACTION_RULES, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig } from '../chunk-
|
|
2
|
+
export { DEFAULT_REACTION_RULES, SpeexorLifecycle, configSchema, costGuardSchema, createEventBus, decompositionSchema, extensionsSchema, generateDefaultConfig, governanceSchema, loadConfig, loggingSchema, performanceSchema, riskPolicySchema, schedulerSchema, securitySchema, validateConfig, worktreeHierarchySchema } from '../chunk-VEZQT5SX.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
|
-
import { S as SpeexorState,
|
|
2
|
-
export {
|
|
1
|
+
import { a as ProviderCostEntry, S as SpeexorState, b as ProjectConfig, A as AgentSession, W as WorktreeSession, R as RuntimeSession, T as TaskGraph, c as AgentEvent, d as ApprovalItem, D as DecisionLogEntry, e as TrackerEvent } from './types-BOMap-tI.js';
|
|
2
|
+
export { f as AgentEventType, g as AgentPlugin, h as AgentProvider, i as AgentStatus, j as AgentTask, k as ApprovalAxis, l as ApprovalStatus, C as CIRun, m as CommandExecution, n as CostGuardConfig, o as DecompositionConfig, E as EventBus, p as ExtensionManifest, q as ExtensionPermissions, r as ExtensionType, s as ExtensionsConfig, G as GovernanceConfig, L as LoggingConfig, N as NotifierPlugin, t as PRComment, u as PRInfo, v as PRStatus, w as PerformanceConfig, x as PermissionAxis, y as PermissionLevel, z as PluginContext, P as PluginModule, B as PluginSlot, F as ProviderRouting, H as ReactionConfig, I as ReactionRule, J as RiskPolicyConfig, K as RollbackRecord, M as RuntimePlugin, O as RuntimeType, Q as SCMPlugin, U as SchedulerConfig, V as SecurityConfig, X as SessionEventType, Y as SessionStatus, Z as SpeexorConfig, _ as TaskNode, $ as TaskNodeStatus, a0 as TaskOrigin, a1 as TaskResult, a2 as TerminalPlugin, a3 as TrackerEventType, a4 as TrackerFilter, a5 as TrackerIssue, a6 as TrackerPlugin, a7 as WorkspacePlugin, a8 as WorkspaceSession, a9 as WorktreeHierarchyConfig } from './types-BOMap-tI.js';
|
|
3
3
|
import { SpeexorLifecycle } from './core/index.js';
|
|
4
|
-
export { DEFAULT_REACTION_RULES, createEventBus, generateDefaultConfig, loadConfig, validateConfig } from './core/index.js';
|
|
4
|
+
export { DEFAULT_REACTION_RULES, configSchema, costGuardSchema, createEventBus, decompositionSchema, extensionsSchema, generateDefaultConfig, governanceSchema, loadConfig, loggingSchema, performanceSchema, riskPolicySchema, schedulerSchema, securitySchema, validateConfig, worktreeHierarchySchema } from './core/index.js';
|
|
5
5
|
export { loadAllPlugins, loadPluginByType } from './plugins/index.js';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
interface CostPanelData {
|
|
9
|
+
totalCost: number;
|
|
10
|
+
byProvider: Array<{
|
|
11
|
+
name: string;
|
|
12
|
+
cost: number;
|
|
13
|
+
}>;
|
|
14
|
+
byProject: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
cost: number;
|
|
17
|
+
}>;
|
|
18
|
+
byTaskNode: Array<{
|
|
19
|
+
taskId: string;
|
|
20
|
+
title: string;
|
|
21
|
+
cost: number;
|
|
22
|
+
}>;
|
|
23
|
+
recentEntries: ProviderCostEntry[];
|
|
24
|
+
budgetExceeded: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ActivityEntry {
|
|
28
|
+
timestamp: Date;
|
|
29
|
+
agentId: string;
|
|
30
|
+
type: string;
|
|
31
|
+
summary: string;
|
|
32
|
+
details?: Record<string, unknown>;
|
|
33
|
+
}
|
|
6
34
|
|
|
7
35
|
declare class DashboardState {
|
|
8
36
|
private state;
|
|
37
|
+
private taskGraphs;
|
|
38
|
+
private agentEvents;
|
|
39
|
+
private approvals;
|
|
40
|
+
private activityFeed;
|
|
41
|
+
private costData;
|
|
42
|
+
private decisionLogEntries;
|
|
9
43
|
private listeners;
|
|
10
44
|
getState(): SpeexorState;
|
|
11
45
|
setProjects(projects: ProjectConfig[]): void;
|
|
@@ -16,9 +50,26 @@ declare class DashboardState {
|
|
|
16
50
|
removeWorktree(sessionId: string): void;
|
|
17
51
|
addRuntime(runtime: RuntimeSession): void;
|
|
18
52
|
removeRuntime(sessionId: string): void;
|
|
53
|
+
updateTaskGraph(graph: TaskGraph): void;
|
|
54
|
+
getTaskGraph(id: string): TaskGraph | undefined;
|
|
55
|
+
getTaskGraphs(): TaskGraph[];
|
|
56
|
+
addAgentEvent(event: AgentEvent): void;
|
|
57
|
+
getAgentEvents(agentId?: string): AgentEvent[];
|
|
58
|
+
getAgentEventsByTask(taskId: string): AgentEvent[];
|
|
59
|
+
addApproval(item: ApprovalItem): void;
|
|
60
|
+
resolveApproval(id: string, status: 'approved' | 'rejected' | 'pending'): void;
|
|
61
|
+
getApprovals(): ApprovalItem[];
|
|
62
|
+
getPendingApprovals(): ApprovalItem[];
|
|
63
|
+
updateActivityFeed(entry: ActivityEntry): void;
|
|
64
|
+
getActivityFeed(limit?: number): ActivityEntry[];
|
|
65
|
+
updateCostData(data: CostPanelData): void;
|
|
66
|
+
getCostData(): CostPanelData | null;
|
|
67
|
+
addDecisionLogEntry(entry: DecisionLogEntry): void;
|
|
68
|
+
getDecisionLogEntries(): DecisionLogEntry[];
|
|
69
|
+
getDecisionLog(): DecisionLogEntry[];
|
|
19
70
|
onUpdate(listener: () => void): () => void;
|
|
20
71
|
private notify;
|
|
21
|
-
toJSON():
|
|
72
|
+
toJSON(): Record<string, unknown>;
|
|
22
73
|
}
|
|
23
74
|
|
|
24
75
|
declare class DashboardServer {
|
|
@@ -27,15 +78,30 @@ declare class DashboardServer {
|
|
|
27
78
|
private state;
|
|
28
79
|
private port;
|
|
29
80
|
private routes;
|
|
81
|
+
private sseClients;
|
|
82
|
+
private heartbeatTimer;
|
|
30
83
|
constructor(lifecycle: SpeexorLifecycle, port?: number);
|
|
31
84
|
private setupRoutes;
|
|
32
85
|
private handleRequest;
|
|
33
86
|
private matchPath;
|
|
34
87
|
private sendJSON;
|
|
88
|
+
private broadcastSSE;
|
|
35
89
|
private handleStatus;
|
|
36
90
|
private handleProjects;
|
|
37
91
|
private handleSessions;
|
|
38
92
|
private handleHealth;
|
|
93
|
+
private handleSSE;
|
|
94
|
+
private handleTaskGraphs;
|
|
95
|
+
private handleTaskGraphById;
|
|
96
|
+
private handleApprovals;
|
|
97
|
+
private handleApproveApproval;
|
|
98
|
+
private handleRejectApproval;
|
|
99
|
+
private handleActivityFeed;
|
|
100
|
+
private handleCost;
|
|
101
|
+
private handleTaskGraphNode;
|
|
102
|
+
private handleDecisionLog;
|
|
103
|
+
private handleRollbackApproval;
|
|
104
|
+
private handleEvalCalibration;
|
|
39
105
|
private serveDashboardHTML;
|
|
40
106
|
start(): void;
|
|
41
107
|
stop(): void;
|
|
@@ -56,20 +122,42 @@ declare class ReactionEngine {
|
|
|
56
122
|
resetRetryCount(key: string): void;
|
|
57
123
|
}
|
|
58
124
|
|
|
125
|
+
/**
|
|
126
|
+
* SessionStore — manages persistent state for Speexor sessions.
|
|
127
|
+
*
|
|
128
|
+
* MIGRATED from sync I/O (readFileSync/writeFileSync) to async I/O per:
|
|
129
|
+
* - PRD06 Track 1.4: "Session store migrasi dari sync I/O ke async"
|
|
130
|
+
* - REFACTOR-LOG: technical debt since v0.1.0
|
|
131
|
+
*
|
|
132
|
+
* This ensures non-blocking I/O for ≥10 concurrent agents (PRD01 NFR).
|
|
133
|
+
*
|
|
134
|
+
* Design: Constructor still uses readFileSync once (necessary sync startup),
|
|
135
|
+
* but ALL subsequent writes use async writeFile + serialized write queue
|
|
136
|
+
* to prevent concurrent write conflicts while keeping event loop responsive.
|
|
137
|
+
*/
|
|
59
138
|
declare class SessionStore {
|
|
60
139
|
private statePath;
|
|
61
140
|
private state;
|
|
141
|
+
private writeQueue;
|
|
62
142
|
constructor(baseDir?: string);
|
|
63
|
-
|
|
64
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Synchronous initial load (called once in constructor).
|
|
145
|
+
* This is the only sync I/O call — acceptable because it runs once at startup.
|
|
146
|
+
*/
|
|
147
|
+
private loadInitialState;
|
|
148
|
+
/**
|
|
149
|
+
* Async save — enqueues writes to prevent concurrent write conflicts
|
|
150
|
+
* while keeping the event loop responsive.
|
|
151
|
+
*/
|
|
152
|
+
private saveAsync;
|
|
65
153
|
getState(): SpeexorState;
|
|
66
|
-
addSession(session: AgentSession): void
|
|
67
|
-
updateSession(sessionId: string, updates: Partial<AgentSession>): void
|
|
68
|
-
removeSession(sessionId: string): void
|
|
69
|
-
addWorktree(worktree: WorktreeSession): void
|
|
70
|
-
removeWorktree(sessionId: string): void
|
|
71
|
-
setProjects(projects: ProjectConfig[]): void
|
|
72
|
-
clear(): void
|
|
154
|
+
addSession(session: AgentSession): Promise<void>;
|
|
155
|
+
updateSession(sessionId: string, updates: Partial<AgentSession>): Promise<void>;
|
|
156
|
+
removeSession(sessionId: string): Promise<void>;
|
|
157
|
+
addWorktree(worktree: WorktreeSession): Promise<void>;
|
|
158
|
+
removeWorktree(sessionId: string): Promise<void>;
|
|
159
|
+
setProjects(projects: ProjectConfig[]): Promise<void>;
|
|
160
|
+
clear(): Promise<void>;
|
|
73
161
|
}
|
|
74
162
|
|
|
75
|
-
export { AgentSession, DashboardServer, DashboardState, ProjectConfig, ReactionEngine, RuntimeSession, SessionStore, SpeexorLifecycle, SpeexorState, TrackerEvent, WorktreeSession, createDashboardServer };
|
|
163
|
+
export { AgentEvent, AgentSession, ApprovalItem, DashboardServer, DashboardState, DecisionLogEntry, ProjectConfig, ProviderCostEntry, ReactionEngine, RuntimeSession, SessionStore, SpeexorLifecycle, SpeexorState, TaskGraph, TrackerEvent, WorktreeSession, createDashboardServer };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import './chunk-5NA2TFPG.js';
|
|
2
|
-
export { DashboardServer, DashboardState, createDashboardServer } from './chunk-
|
|
3
|
-
export { DEFAULT_REACTION_RULES, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig } from './chunk-
|
|
4
|
-
export { loadAllPlugins, loadPluginByType } from './chunk-
|
|
2
|
+
export { DashboardServer, DashboardState, createDashboardServer } from './chunk-GOGI3JQD.js';
|
|
3
|
+
export { DEFAULT_REACTION_RULES, SpeexorLifecycle, configSchema, costGuardSchema, createEventBus, decompositionSchema, extensionsSchema, generateDefaultConfig, governanceSchema, loadConfig, loggingSchema, performanceSchema, riskPolicySchema, schedulerSchema, securitySchema, validateConfig, worktreeHierarchySchema } from './chunk-VEZQT5SX.js';
|
|
4
|
+
export { loadAllPlugins, loadPluginByType } from './chunk-5OD5UWB5.js';
|
|
5
5
|
import Debug from 'debug';
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
7
|
+
import { writeFile } from 'fs/promises';
|
|
7
8
|
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import 'crypto';
|
|
8
11
|
|
|
9
12
|
var debug = Debug("speexor:reaction");
|
|
10
13
|
var ReactionEngine = class {
|
|
@@ -120,19 +123,35 @@ Data: ${JSON.stringify(event.data)}`,
|
|
|
120
123
|
this.retryCounts.delete(key);
|
|
121
124
|
}
|
|
122
125
|
};
|
|
123
|
-
var debug2 = Debug("speexor:
|
|
126
|
+
var debug2 = Debug("speexor:data-dir");
|
|
127
|
+
function resolveBaseDir(cwdDir) {
|
|
128
|
+
const homeDir = join(homedir(), ".speexor");
|
|
129
|
+
if (existsSync(homeDir)) {
|
|
130
|
+
debug2(`Using canonical data dir: ${homeDir}`);
|
|
131
|
+
return homeDir;
|
|
132
|
+
}
|
|
133
|
+
const fallback = cwdDir ?? join(process.cwd(), ".speexor");
|
|
134
|
+
debug2(`Falling back to CWD data dir: ${fallback}`);
|
|
135
|
+
return fallback;
|
|
136
|
+
}
|
|
137
|
+
var debug3 = Debug("speexor:store");
|
|
124
138
|
var SessionStore = class {
|
|
125
139
|
statePath;
|
|
126
140
|
state;
|
|
141
|
+
writeQueue = Promise.resolve();
|
|
127
142
|
constructor(baseDir) {
|
|
128
|
-
const dir = baseDir
|
|
143
|
+
const dir = resolveBaseDir(baseDir);
|
|
129
144
|
if (!existsSync(dir)) {
|
|
130
145
|
mkdirSync(dir, { recursive: true });
|
|
131
146
|
}
|
|
132
147
|
this.statePath = join(dir, "state.json");
|
|
133
|
-
this.state = this.
|
|
148
|
+
this.state = this.loadInitialState();
|
|
134
149
|
}
|
|
135
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Synchronous initial load (called once in constructor).
|
|
152
|
+
* This is the only sync I/O call — acceptable because it runs once at startup.
|
|
153
|
+
*/
|
|
154
|
+
loadInitialState() {
|
|
136
155
|
try {
|
|
137
156
|
if (existsSync(this.statePath)) {
|
|
138
157
|
const raw = readFileSync(this.statePath, "utf-8");
|
|
@@ -145,25 +164,32 @@ var SessionStore = class {
|
|
|
145
164
|
};
|
|
146
165
|
}
|
|
147
166
|
} catch (error) {
|
|
148
|
-
|
|
167
|
+
debug3(`Failed to load state: ${error}`);
|
|
149
168
|
}
|
|
150
169
|
return { sessions: [], worktrees: [], runtimes: [], projects: [] };
|
|
151
170
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Async save — enqueues writes to prevent concurrent write conflicts
|
|
173
|
+
* while keeping the event loop responsive.
|
|
174
|
+
*/
|
|
175
|
+
async saveAsync() {
|
|
176
|
+
this.writeQueue = this.writeQueue.then(async () => {
|
|
177
|
+
try {
|
|
178
|
+
await writeFile(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
179
|
+
} catch (error) {
|
|
180
|
+
debug3(`Failed to save state: ${error}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return this.writeQueue;
|
|
158
184
|
}
|
|
159
185
|
getState() {
|
|
160
186
|
return { ...this.state };
|
|
161
187
|
}
|
|
162
|
-
addSession(session) {
|
|
188
|
+
async addSession(session) {
|
|
163
189
|
this.state.sessions.push(session);
|
|
164
|
-
this.
|
|
190
|
+
await this.saveAsync();
|
|
165
191
|
}
|
|
166
|
-
updateSession(sessionId, updates) {
|
|
192
|
+
async updateSession(sessionId, updates) {
|
|
167
193
|
const idx = this.state.sessions.findIndex((s) => s.id === sessionId);
|
|
168
194
|
if (idx !== -1) {
|
|
169
195
|
const existing = this.state.sessions[idx];
|
|
@@ -174,29 +200,29 @@ var SessionStore = class {
|
|
|
174
200
|
if (updates.status !== void 0) existing.status = updates.status;
|
|
175
201
|
if (updates.startedAt !== void 0) existing.startedAt = updates.startedAt;
|
|
176
202
|
if (updates.runtimeSessionId !== void 0) existing.runtimeSessionId = updates.runtimeSessionId;
|
|
177
|
-
this.
|
|
203
|
+
await this.saveAsync();
|
|
178
204
|
}
|
|
179
205
|
}
|
|
180
206
|
}
|
|
181
|
-
removeSession(sessionId) {
|
|
207
|
+
async removeSession(sessionId) {
|
|
182
208
|
this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId);
|
|
183
|
-
this.
|
|
209
|
+
await this.saveAsync();
|
|
184
210
|
}
|
|
185
|
-
addWorktree(worktree) {
|
|
211
|
+
async addWorktree(worktree) {
|
|
186
212
|
this.state.worktrees.push(worktree);
|
|
187
|
-
this.
|
|
213
|
+
await this.saveAsync();
|
|
188
214
|
}
|
|
189
|
-
removeWorktree(sessionId) {
|
|
215
|
+
async removeWorktree(sessionId) {
|
|
190
216
|
this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId);
|
|
191
|
-
this.
|
|
217
|
+
await this.saveAsync();
|
|
192
218
|
}
|
|
193
|
-
setProjects(projects) {
|
|
219
|
+
async setProjects(projects) {
|
|
194
220
|
this.state.projects = projects;
|
|
195
|
-
this.
|
|
221
|
+
await this.saveAsync();
|
|
196
222
|
}
|
|
197
|
-
clear() {
|
|
223
|
+
async clear() {
|
|
198
224
|
this.state = { sessions: [], worktrees: [], runtimes: [], projects: [] };
|
|
199
|
-
this.
|
|
225
|
+
await this.saveAsync();
|
|
200
226
|
}
|
|
201
227
|
};
|
|
202
228
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reaction/engine.ts","../src/store/session.ts"],"names":["debug","Debug"],"mappings":"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","file":"index.js","sourcesContent":["import type { SpeexorLifecycle } from '../core/lifecycle.js'\nimport type { ReactionRule, TrackerEvent, ProjectConfig, AgentTask } from '../core/types.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:reaction')\n\ninterface ReactionHandler {\n eventType: string\n rule: ReactionRule\n project: ProjectConfig\n handle(event: TrackerEvent): Promise<void>\n}\n\nexport class ReactionEngine {\n private lifecycle: SpeexorLifecycle\n private handlers: ReactionHandler[] = []\n private retryCounts = new Map<string, number>()\n\n constructor(lifecycle: SpeexorLifecycle) {\n this.lifecycle = lifecycle\n\n // Listen to lifecycle events\n lifecycle.eventBus.on('reaction:triggered', (data: unknown) => {\n const { eventType, taskId } = data as { eventType: string; taskId: string }\n debug(`Reaction triggered: ${eventType} for task ${taskId}`)\n })\n }\n\n configure(projects: ProjectConfig[]): void {\n this.handlers = []\n\n for (const project of projects) {\n if (!project.reactions) continue\n\n for (const [eventType, rule] of Object.entries(project.reactions)) {\n if (!rule) continue\n\n this.handlers.push({\n eventType,\n rule,\n project,\n handle: async (event: TrackerEvent) => {\n await this.executeReaction(event, rule, project)\n },\n })\n }\n }\n\n debug(`Configured ${this.handlers.length} reaction handler(s)`)\n }\n\n async processEvent(event: TrackerEvent): Promise<void> {\n debug(`Processing event: ${event.type} for issue ${event.issueId}`)\n\n const matchingHandlers = this.handlers.filter((h) => h.eventType === event.type)\n\n for (const handler of matchingHandlers) {\n const key = `${event.issueId}:${handler.eventType}`\n const retries = this.retryCounts.get(key) ?? 0\n\n if (retries >= handler.rule.retries) {\n debug(`Max retries reached for ${key} (${retries}/${handler.rule.retries})`)\n if (handler.rule.action === 'escalate') {\n this.escalate(event, handler.project)\n }\n continue\n }\n\n try {\n await handler.handle(event)\n this.retryCounts.set(key, retries + 1)\n } catch (error) {\n debug(`Reaction failed for ${key}: ${error}`)\n }\n }\n }\n\n private async executeReaction(event: TrackerEvent, rule: ReactionRule, project: ProjectConfig): Promise<void> {\n if (!rule.auto) {\n debug(`Rule for ${event.type} is not auto — notifying instead`)\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'notify',\n })\n return\n }\n\n switch (rule.action) {\n case 'fix':\n await this.autoFix(event, project)\n break\n case 'notify':\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'notify',\n })\n break\n case 'escalate':\n this.escalate(event, project)\n break\n case 'skip':\n debug(`Skipping reaction for ${event.type}`)\n break\n }\n }\n\n private async autoFix(event: TrackerEvent, project: ProjectConfig): Promise<void> {\n const task: AgentTask = {\n id: `fix-${event.issueId}-${Date.now()}`,\n title: `Auto-fix: ${event.type} for issue ${event.issueId}`,\n description: `Automated fix triggered by event: ${event.type}\\nIssue: ${event.issueId}\\nData: ${JSON.stringify(event.data)}`,\n repository: project.repository,\n branch: `speexor/fix-${event.issueId}`,\n provider: project.provider.primary,\n }\n\n debug(`Auto-fix spawning agent for task ${task.id}`)\n\n try {\n await this.lifecycle.spawnAgent(task)\n } catch (error) {\n debug(`Auto-fix spawn failed: ${error}`)\n }\n }\n\n private escalate(event: TrackerEvent, _project: ProjectConfig): void {\n debug(`Escalating: ${event.type} for issue ${event.issueId}`)\n\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'escalate',\n message: `Max retries exceeded for ${event.type} on issue ${event.issueId}`,\n })\n }\n\n getRetryCount(key: string): number {\n return this.retryCounts.get(key) ?? 0\n }\n\n resetRetryCount(key: string): void {\n this.retryCounts.delete(key)\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { SpeexorState, AgentSession, WorktreeSession, ProjectConfig } from '../core/types.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:store')\n\nexport class SessionStore {\n private statePath: string\n private state: SpeexorState\n\n constructor(baseDir?: string) {\n const dir = baseDir ?? join(process.cwd(), '.speexor')\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n\n this.statePath = join(dir, 'state.json')\n this.state = this.load()\n }\n\n private load(): SpeexorState {\n try {\n if (existsSync(this.statePath)) {\n const raw = readFileSync(this.statePath, 'utf-8')\n const data = JSON.parse(raw)\n return {\n sessions: data.sessions ?? [],\n worktrees: data.worktrees ?? [],\n runtimes: data.runtimes ?? [],\n projects: data.projects ?? [],\n }\n }\n } catch (error) {\n debug(`Failed to load state: ${error}`)\n }\n\n return { sessions: [], worktrees: [], runtimes: [], projects: [] }\n }\n\n private save(): void {\n try {\n writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), 'utf-8')\n } catch (error) {\n debug(`Failed to save state: ${error}`)\n }\n }\n\n getState(): SpeexorState {\n return { ...this.state }\n }\n\n addSession(session: AgentSession): void {\n this.state.sessions.push(session)\n this.save()\n }\n\n updateSession(sessionId: string, updates: Partial<AgentSession>): void {\n const idx = this.state.sessions.findIndex((s) => s.id === sessionId)\n if (idx !== -1) {\n const existing = this.state.sessions[idx]\n if (existing) {\n if (updates.id !== undefined) existing.id = updates.id\n if (updates.taskId !== undefined) existing.taskId = updates.taskId\n if (updates.provider !== undefined) existing.provider = updates.provider\n if (updates.status !== undefined) existing.status = updates.status\n if (updates.startedAt !== undefined) existing.startedAt = updates.startedAt\n if (updates.runtimeSessionId !== undefined) existing.runtimeSessionId = updates.runtimeSessionId\n this.save()\n }\n }\n }\n\n removeSession(sessionId: string): void {\n this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId)\n this.save()\n }\n\n addWorktree(worktree: WorktreeSession): void {\n this.state.worktrees.push(worktree)\n this.save()\n }\n\n removeWorktree(sessionId: string): void {\n this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId)\n this.save()\n }\n\n setProjects(projects: ProjectConfig[]): void {\n this.state.projects = projects\n this.save()\n }\n\n clear(): void {\n this.state = { sessions: [], worktrees: [], runtimes: [], projects: [] }\n this.save()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/reaction/engine.ts","../src/core/data-dir.ts","../src/store/session.ts"],"names":["debug","Debug","existsSync","join"],"mappings":"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","file":"index.js","sourcesContent":["import type { SpeexorLifecycle } from '../core/lifecycle.js'\r\nimport type { ReactionRule, TrackerEvent, ProjectConfig, AgentTask } from '../core/types.js'\r\nimport Debug from 'debug'\r\n\r\nconst debug = Debug('speexor:reaction')\r\n\r\ninterface ReactionHandler {\r\n eventType: string\r\n rule: ReactionRule\r\n project: ProjectConfig\r\n handle(event: TrackerEvent): Promise<void>\r\n}\r\n\r\nexport class ReactionEngine {\r\n private lifecycle: SpeexorLifecycle\r\n private handlers: ReactionHandler[] = []\r\n private retryCounts = new Map<string, number>()\r\n\r\n constructor(lifecycle: SpeexorLifecycle) {\r\n this.lifecycle = lifecycle\r\n\r\n // Listen to lifecycle events\r\n lifecycle.eventBus.on('reaction:triggered', (data: unknown) => {\r\n const { eventType, taskId } = data as { eventType: string; taskId: string }\r\n debug(`Reaction triggered: ${eventType} for task ${taskId}`)\r\n })\r\n }\r\n\r\n configure(projects: ProjectConfig[]): void {\r\n this.handlers = []\r\n\r\n for (const project of projects) {\r\n if (!project.reactions) continue\r\n\r\n for (const [eventType, rule] of Object.entries(project.reactions)) {\r\n if (!rule) continue\r\n\r\n this.handlers.push({\r\n eventType,\r\n rule,\r\n project,\r\n handle: async (event: TrackerEvent) => {\r\n await this.executeReaction(event, rule, project)\r\n },\r\n })\r\n }\r\n }\r\n\r\n debug(`Configured ${this.handlers.length} reaction handler(s)`)\r\n }\r\n\r\n async processEvent(event: TrackerEvent): Promise<void> {\r\n debug(`Processing event: ${event.type} for issue ${event.issueId}`)\r\n\r\n const matchingHandlers = this.handlers.filter((h) => h.eventType === event.type)\r\n\r\n for (const handler of matchingHandlers) {\r\n const key = `${event.issueId}:${handler.eventType}`\r\n const retries = this.retryCounts.get(key) ?? 0\r\n\r\n if (retries >= handler.rule.retries) {\r\n debug(`Max retries reached for ${key} (${retries}/${handler.rule.retries})`)\r\n if (handler.rule.action === 'escalate') {\r\n this.escalate(event, handler.project)\r\n }\r\n continue\r\n }\r\n\r\n try {\r\n await handler.handle(event)\r\n this.retryCounts.set(key, retries + 1)\r\n } catch (error) {\r\n debug(`Reaction failed for ${key}: ${error}`)\r\n }\r\n }\r\n }\r\n\r\n private async executeReaction(event: TrackerEvent, rule: ReactionRule, project: ProjectConfig): Promise<void> {\r\n if (!rule.auto) {\r\n debug(`Rule for ${event.type} is not auto — notifying instead`)\r\n this.lifecycle.eventBus.emit('reaction:triggered', {\r\n eventType: event.type,\r\n taskId: event.issueId,\r\n action: 'notify',\r\n })\r\n return\r\n }\r\n\r\n switch (rule.action) {\r\n case 'fix':\r\n await this.autoFix(event, project)\r\n break\r\n case 'notify':\r\n this.lifecycle.eventBus.emit('reaction:triggered', {\r\n eventType: event.type,\r\n taskId: event.issueId,\r\n action: 'notify',\r\n })\r\n break\r\n case 'escalate':\r\n this.escalate(event, project)\r\n break\r\n case 'skip':\r\n debug(`Skipping reaction for ${event.type}`)\r\n break\r\n }\r\n }\r\n\r\n private async autoFix(event: TrackerEvent, project: ProjectConfig): Promise<void> {\r\n const task: AgentTask = {\r\n id: `fix-${event.issueId}-${Date.now()}`,\r\n title: `Auto-fix: ${event.type} for issue ${event.issueId}`,\r\n description: `Automated fix triggered by event: ${event.type}\\nIssue: ${event.issueId}\\nData: ${JSON.stringify(event.data)}`,\r\n repository: project.repository,\r\n branch: `speexor/fix-${event.issueId}`,\r\n provider: project.provider.primary,\r\n }\r\n\r\n debug(`Auto-fix spawning agent for task ${task.id}`)\r\n\r\n try {\r\n await this.lifecycle.spawnAgent(task)\r\n } catch (error) {\r\n debug(`Auto-fix spawn failed: ${error}`)\r\n }\r\n }\r\n\r\n private escalate(event: TrackerEvent, _project: ProjectConfig): void {\r\n debug(`Escalating: ${event.type} for issue ${event.issueId}`)\r\n\r\n this.lifecycle.eventBus.emit('reaction:triggered', {\r\n eventType: event.type,\r\n taskId: event.issueId,\r\n action: 'escalate',\r\n message: `Max retries exceeded for ${event.type} on issue ${event.issueId}`,\r\n })\r\n }\r\n\r\n getRetryCount(key: string): number {\r\n return this.retryCounts.get(key) ?? 0\r\n }\r\n\r\n resetRetryCount(key: string): void {\r\n this.retryCounts.delete(key)\r\n }\r\n}\r\n","import { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { createHash } from 'node:crypto'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:data-dir')\n\nfunction hashProjectId(projectId: string): string {\n return createHash('sha256').update(projectId).digest('hex').substring(0, 8)\n}\n\nexport function getSpeexorDataDir(projectId?: string): string {\n const home = homedir()\n const base = join(home, '.speexor')\n\n if (projectId) {\n const h = hashProjectId(projectId)\n return join(base, `${h}-${projectId}`)\n }\n\n return base\n}\n\nexport function resolveBaseDir(cwdDir?: string): string {\n const homeDir = join(homedir(), '.speexor')\n if (existsSync(homeDir)) {\n debug(`Using canonical data dir: ${homeDir}`)\n return homeDir\n }\n const fallback = cwdDir ?? join(process.cwd(), '.speexor')\n debug(`Falling back to CWD data dir: ${fallback}`)\n return fallback\n}\n","import { existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { SpeexorState, AgentSession, WorktreeSession, ProjectConfig } from '../core/types.js'\nimport { resolveBaseDir } from '../core/data-dir.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:store')\n\n/**\n * SessionStore — manages persistent state for Speexor sessions.\n *\n * MIGRATED from sync I/O (readFileSync/writeFileSync) to async I/O per:\n * - PRD06 Track 1.4: \"Session store migrasi dari sync I/O ke async\"\n * - REFACTOR-LOG: technical debt since v0.1.0\n *\n * This ensures non-blocking I/O for ≥10 concurrent agents (PRD01 NFR).\n *\n * Design: Constructor still uses readFileSync once (necessary sync startup),\n * but ALL subsequent writes use async writeFile + serialized write queue\n * to prevent concurrent write conflicts while keeping event loop responsive.\n */\nexport class SessionStore {\n private statePath: string\n private state: SpeexorState\n private writeQueue: Promise<void> = Promise.resolve()\n\n constructor(baseDir?: string) {\n const dir = resolveBaseDir(baseDir)\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n\n this.statePath = join(dir, 'state.json')\n this.state = this.loadInitialState()\n }\n\n /**\n * Synchronous initial load (called once in constructor).\n * This is the only sync I/O call — acceptable because it runs once at startup.\n */\n private loadInitialState(): SpeexorState {\n try {\n if (existsSync(this.statePath)) {\n const raw = readFileSync(this.statePath, 'utf-8')\n const data = JSON.parse(raw)\n return {\n sessions: data.sessions ?? [],\n worktrees: data.worktrees ?? [],\n runtimes: data.runtimes ?? [],\n projects: data.projects ?? [],\n }\n }\n } catch (error) {\n debug(`Failed to load state: ${error}`)\n }\n\n return { sessions: [], worktrees: [], runtimes: [], projects: [] }\n }\n\n /**\n * Async save — enqueues writes to prevent concurrent write conflicts\n * while keeping the event loop responsive.\n */\n private async saveAsync(): Promise<void> {\n this.writeQueue = this.writeQueue.then(async () => {\n try {\n await writeFile(this.statePath, JSON.stringify(this.state, null, 2), 'utf-8')\n } catch (error) {\n debug(`Failed to save state: ${error}`)\n }\n })\n return this.writeQueue\n }\n\n getState(): SpeexorState {\n return { ...this.state }\n }\n\n async addSession(session: AgentSession): Promise<void> {\n this.state.sessions.push(session)\n await this.saveAsync()\n }\n\n async updateSession(sessionId: string, updates: Partial<AgentSession>): Promise<void> {\n const idx = this.state.sessions.findIndex((s) => s.id === sessionId)\n if (idx !== -1) {\n const existing = this.state.sessions[idx]\n if (existing) {\n if (updates.id !== undefined) existing.id = updates.id\n if (updates.taskId !== undefined) existing.taskId = updates.taskId\n if (updates.provider !== undefined) existing.provider = updates.provider\n if (updates.status !== undefined) existing.status = updates.status\n if (updates.startedAt !== undefined) existing.startedAt = updates.startedAt\n if (updates.runtimeSessionId !== undefined) existing.runtimeSessionId = updates.runtimeSessionId\n await this.saveAsync()\n }\n }\n }\n\n async removeSession(sessionId: string): Promise<void> {\n this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId)\n await this.saveAsync()\n }\n\n async addWorktree(worktree: WorktreeSession): Promise<void> {\n this.state.worktrees.push(worktree)\n await this.saveAsync()\n }\n\n async removeWorktree(sessionId: string): Promise<void> {\n this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId)\n await this.saveAsync()\n }\n\n async setProjects(projects: ProjectConfig[]): Promise<void> {\n this.state.projects = projects\n await this.saveAsync()\n }\n\n async clear(): Promise<void> {\n this.state = { sessions: [], worktrees: [], runtimes: [], projects: [] }\n await this.saveAsync()\n }\n}\n"]}
|
package/dist/plugins/index.d.ts
CHANGED
package/dist/plugins/index.js
CHANGED