pi-harness-runtime 1.1.335 → 1.1.337

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 (180) hide show
  1. package/index.ts +21 -12
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/package.json +1 -1
  5. package/packages/architecture-generator/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-refactor/package.json +1 -1
  8. package/packages/autonomous-runtime/package.json +1 -1
  9. package/packages/cache-strategy/package.json +1 -1
  10. package/packages/capability-registry/package.json +1 -1
  11. package/packages/checkpoint/package.json +1 -1
  12. package/packages/cli-plugin-sdk/package.json +1 -1
  13. package/packages/clipboard/package.json +1 -1
  14. package/packages/clipboard-plugin/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/codex-watcher/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/connection-runtime/dist/index.d.ts +11 -0
  21. package/packages/connection-runtime/dist/index.js +11 -0
  22. package/packages/connection-runtime/dist/registry.d.ts +69 -0
  23. package/packages/connection-runtime/dist/registry.js +117 -0
  24. package/packages/connection-runtime/dist/supervisor.d.ts +89 -0
  25. package/packages/connection-runtime/dist/supervisor.js +169 -0
  26. package/packages/connection-runtime/dist/types.d.ts +70 -0
  27. package/packages/connection-runtime/dist/types.js +16 -0
  28. package/packages/connection-runtime/package.json +24 -0
  29. package/packages/connection-runtime/src/index.ts +12 -0
  30. package/packages/connection-runtime/src/registry.ts +142 -0
  31. package/packages/connection-runtime/src/supervisor.ts +224 -0
  32. package/packages/connection-runtime/src/types.ts +88 -0
  33. package/packages/context-compiler/package.json +1 -1
  34. package/packages/context-discovery/package.json +1 -1
  35. package/packages/context-manager/package.json +1 -1
  36. package/packages/contracts/dist/approvals.d.ts +273 -0
  37. package/packages/contracts/dist/approvals.js +93 -0
  38. package/packages/contracts/dist/base.d.ts +78 -0
  39. package/packages/contracts/dist/base.js +82 -0
  40. package/packages/contracts/dist/events.d.ts +99 -0
  41. package/packages/contracts/dist/events.js +65 -0
  42. package/packages/contracts/dist/index.d.ts +20 -0
  43. package/packages/contracts/dist/index.js +20 -0
  44. package/packages/contracts/dist/jobs.d.ts +234 -0
  45. package/packages/contracts/dist/jobs.js +79 -0
  46. package/packages/contracts/dist/notifications.d.ts +317 -0
  47. package/packages/contracts/dist/notifications.js +85 -0
  48. package/packages/contracts/dist/projects.d.ts +171 -0
  49. package/packages/contracts/dist/projects.js +62 -0
  50. package/packages/contracts/dist/sessions.d.ts +226 -0
  51. package/packages/contracts/dist/sessions.js +82 -0
  52. package/packages/contracts/package.json +24 -0
  53. package/packages/contracts/src/approvals.ts +115 -0
  54. package/packages/contracts/src/base.ts +126 -0
  55. package/packages/contracts/src/events.ts +78 -0
  56. package/packages/contracts/src/index.ts +21 -0
  57. package/packages/contracts/src/jobs.ts +101 -0
  58. package/packages/contracts/src/notifications.ts +104 -0
  59. package/packages/contracts/src/projects.ts +81 -0
  60. package/packages/contracts/src/sessions.ts +104 -0
  61. package/packages/cookie-sanitizer/package.json +1 -1
  62. package/packages/cost-optimizer/package.json +1 -1
  63. package/packages/dependency-analyzer/package.json +1 -1
  64. package/packages/django-plugin/package.json +1 -1
  65. package/packages/doc-generator/package.json +1 -1
  66. package/packages/durable-review/package.json +1 -1
  67. package/packages/e2e-tester/package.json +1 -1
  68. package/packages/evaluation-engine/package.json +1 -1
  69. package/packages/evaluation-runner/package.json +1 -1
  70. package/packages/event-bus/package.json +1 -1
  71. package/packages/event-sink/dist/index.d.ts +12 -0
  72. package/packages/event-sink/dist/index.js +12 -0
  73. package/packages/event-sink/dist/outbox.d.ts +51 -0
  74. package/packages/event-sink/dist/outbox.js +88 -0
  75. package/packages/event-sink/dist/projection.d.ts +41 -0
  76. package/packages/event-sink/dist/projection.js +62 -0
  77. package/packages/event-sink/dist/sink.d.ts +81 -0
  78. package/packages/event-sink/dist/sink.js +106 -0
  79. package/packages/event-sink/package.json +28 -0
  80. package/packages/event-sink/src/index.ts +15 -0
  81. package/packages/event-sink/src/outbox.ts +119 -0
  82. package/packages/event-sink/src/projection.ts +84 -0
  83. package/packages/event-sink/src/sink.ts +167 -0
  84. package/packages/event-store/package.json +1 -1
  85. package/packages/experience-replay/package.json +1 -1
  86. package/packages/feedback-collector/package.json +1 -1
  87. package/packages/file-copy-helper/package.json +1 -1
  88. package/packages/framework-detector/package.json +1 -1
  89. package/packages/framework-plugin-sdk/package.json +1 -1
  90. package/packages/frappe-plugin/package.json +1 -1
  91. package/packages/generic-web-plugin/package.json +1 -1
  92. package/packages/health-monitor/package.json +1 -1
  93. package/packages/intent-analyzer/package.json +1 -1
  94. package/packages/jev-judge/package.json +1 -1
  95. package/packages/knowledge-graph/package.json +1 -1
  96. package/packages/knowledge-retrieval/package.json +1 -1
  97. package/packages/laravel-plugin/package.json +1 -1
  98. package/packages/learning-engine/package.json +1 -1
  99. package/packages/mcp-adapter/package.json +1 -1
  100. package/packages/memory-engine/package.json +1 -1
  101. package/packages/milestone-manager/package.json +1 -1
  102. package/packages/model-registry/package.json +1 -1
  103. package/packages/nextjs-plugin/package.json +1 -1
  104. package/packages/notification/package.json +1 -1
  105. package/packages/observability/package.json +1 -1
  106. package/packages/okf-indexer/package.json +1 -1
  107. package/packages/paperclip-integration/package.json +1 -1
  108. package/packages/performance-optimizer/package.json +1 -1
  109. package/packages/permission-mode/dist/checker.d.ts +66 -0
  110. package/packages/permission-mode/dist/checker.js +64 -0
  111. package/packages/permission-mode/dist/index.d.ts +12 -0
  112. package/packages/permission-mode/dist/index.js +11 -0
  113. package/packages/permission-mode/dist/manager.d.ts +65 -0
  114. package/packages/permission-mode/dist/manager.js +91 -0
  115. package/packages/permission-mode/dist/mode.d.ts +31 -0
  116. package/packages/permission-mode/dist/mode.js +47 -0
  117. package/packages/permission-mode/package.json +24 -0
  118. package/packages/permission-mode/src/checker.ts +103 -0
  119. package/packages/permission-mode/src/index.ts +13 -0
  120. package/packages/permission-mode/src/manager.ts +154 -0
  121. package/packages/permission-mode/src/mode.ts +73 -0
  122. package/packages/privilege-broker/package.json +1 -1
  123. package/packages/project-analyzer/package.json +1 -1
  124. package/packages/project-bootstrap/package.json +1 -1
  125. package/packages/projection-engine/package.json +1 -1
  126. package/packages/prompt-compiler/package.json +1 -1
  127. package/packages/prompt-versioning/package.json +1 -1
  128. package/packages/provider-adapter-sdk/package.json +1 -1
  129. package/packages/provider-router/package.json +1 -1
  130. package/packages/provider-selector/package.json +1 -1
  131. package/packages/providers/package.json +1 -1
  132. package/packages/qdrant-skills/package.json +1 -1
  133. package/packages/quota-manager/package.json +1 -1
  134. package/packages/rate-limiter/package.json +1 -1
  135. package/packages/react-vite-plugin/package.json +1 -1
  136. package/packages/relay/dist/client.d.ts +53 -0
  137. package/packages/relay/dist/client.js +95 -0
  138. package/packages/relay/dist/index.d.ts +9 -0
  139. package/packages/relay/dist/index.js +8 -0
  140. package/packages/relay/dist/server.d.ts +58 -0
  141. package/packages/relay/dist/server.js +93 -0
  142. package/packages/relay/dist/types.d.ts +35 -0
  143. package/packages/relay/dist/types.js +4 -0
  144. package/packages/relay/package.json +24 -0
  145. package/packages/relay/src/client.ts +119 -0
  146. package/packages/relay/src/index.ts +10 -0
  147. package/packages/relay/src/server.ts +120 -0
  148. package/packages/relay/src/types.ts +38 -0
  149. package/packages/release-manager/package.json +1 -1
  150. package/packages/requirement-compiler/package.json +1 -1
  151. package/packages/runtime/package.json +1 -1
  152. package/packages/scheduler/package.json +1 -1
  153. package/packages/scheduler-adapter/package.json +1 -1
  154. package/packages/session/package.json +1 -1
  155. package/packages/session-api/package.json +1 -1
  156. package/packages/session-export/package.json +1 -1
  157. package/packages/shared-context/package.json +1 -1
  158. package/packages/skill-mcp-client/package.json +1 -1
  159. package/packages/skill-registry/package.json +1 -1
  160. package/packages/skills/package.json +1 -1
  161. package/packages/sprint-planner/package.json +1 -1
  162. package/packages/subscription-engine/package.json +1 -1
  163. package/packages/task-compiler/package.json +1 -1
  164. package/packages/telegram/package.json +1 -1
  165. package/packages/tencentdb-memory/package.json +1 -1
  166. package/packages/tencentdb-sync/package.json +1 -1
  167. package/packages/test-data-generator/package.json +1 -1
  168. package/packages/test-generator/package.json +1 -1
  169. package/packages/todo-bd-sync/package.json +1 -1
  170. package/packages/token-estimation/package.json +1 -1
  171. package/packages/token-optimizer/package.json +1 -1
  172. package/packages/trajectory/package.json +1 -1
  173. package/packages/tui/package.json +1 -1
  174. package/packages/types/package.json +1 -1
  175. package/packages/webmcp-tester/package.json +1 -1
  176. package/packages/wiki/package.json +1 -1
  177. package/packages/workflow-events/package.json +1 -1
  178. package/packages/workspace-scanner/package.json +1 -1
  179. package/packages/worktree/package.json +1 -1
  180. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Projection Store
3
+ *
4
+ * Maintains materialized views of events for efficient queries.
5
+ * Projections are updated atomically with events.
6
+ */
7
+ /**
8
+ * Projection Store
9
+ */
10
+ export class ProjectionStore {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ projections = new Map();
13
+ /**
14
+ * Register a projection
15
+ */
16
+ register(projection) {
17
+ this.projections.set(projection.name, {
18
+ name: projection.name,
19
+ state: projection.initialState,
20
+ initialState: projection.initialState,
21
+ reduce: projection.reduce,
22
+ });
23
+ }
24
+ /**
25
+ * Apply events to all projections
26
+ */
27
+ apply(events) {
28
+ for (const event of events) {
29
+ for (const [name, entry] of this.projections.entries()) {
30
+ try {
31
+ entry.state = entry.reduce(entry.state, event);
32
+ }
33
+ catch {
34
+ // Silently ignore projection errors - don't crash the event loop
35
+ // callers can check projection state after apply()
36
+ }
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Get projection state
42
+ */
43
+ get(name) {
44
+ const entry = this.projections.get(name);
45
+ return entry ? entry.state : null;
46
+ }
47
+ /**
48
+ * Reset projection to initial state
49
+ */
50
+ reset(name) {
51
+ const entry = this.projections.get(name);
52
+ if (entry) {
53
+ entry.state = entry.initialState;
54
+ }
55
+ }
56
+ /**
57
+ * List all projection names
58
+ */
59
+ list() {
60
+ return Array.from(this.projections.keys());
61
+ }
62
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Event Sink - Atomic Event Sourcing
3
+ *
4
+ * Inspired by T3Code's orchestrator:
5
+ * - Events + projections + outbox effects committed atomically
6
+ * - Command acknowledgement = intent committed, not work finished
7
+ */
8
+ /**
9
+ * Event with metadata
10
+ */
11
+ export interface StoredEvent {
12
+ id: string;
13
+ type: string;
14
+ timestamp: string;
15
+ workspaceId?: string;
16
+ sessionId?: string;
17
+ jobId?: string;
18
+ payload?: Record<string, unknown>;
19
+ metadata?: Record<string, unknown>;
20
+ commandId?: string;
21
+ sequence: number;
22
+ }
23
+ /**
24
+ * Outbox entry for side effects
25
+ */
26
+ export interface OutboxEntry {
27
+ id: string;
28
+ eventId: string;
29
+ effect: string;
30
+ payload: unknown;
31
+ status: 'pending' | 'processing' | 'completed' | 'failed';
32
+ attempts: number;
33
+ lastError?: string;
34
+ createdAt: string;
35
+ processedAt?: string;
36
+ }
37
+ /**
38
+ * Event Sink options
39
+ */
40
+ export interface EventSinkOptions {
41
+ /** Database file path */
42
+ dbPath: string;
43
+ /** Called when events are committed */
44
+ onEventsCommitted?: (events: StoredEvent[]) => void;
45
+ }
46
+ /**
47
+ * Event Sink - Atomic event sourcing
48
+ */
49
+ export declare class EventSink {
50
+ private db;
51
+ private sequence;
52
+ private onEventsCommitted?;
53
+ constructor(options: EventSinkOptions);
54
+ /**
55
+ * Commit events atomically with projections and outbox
56
+ */
57
+ commit<T extends StoredEvent>(events: T[], outboxEffects?: Array<{
58
+ effect: string;
59
+ payload: unknown;
60
+ }>): Promise<{
61
+ events: StoredEvent[];
62
+ outbox: OutboxEntry[];
63
+ }>;
64
+ /**
65
+ * Get events by filter
66
+ */
67
+ getEvents(filter?: {
68
+ types?: string[];
69
+ since?: number;
70
+ until?: number;
71
+ limit?: number;
72
+ }): Promise<StoredEvent[]>;
73
+ /**
74
+ * Get pending outbox entries
75
+ */
76
+ getPendingOutbox(limit?: number): Promise<OutboxEntry[]>;
77
+ /**
78
+ * Update outbox entry status
79
+ */
80
+ updateOutboxStatus(id: string, status: OutboxEntry['status'], error?: string): Promise<void>;
81
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Event Sink - Atomic Event Sourcing
3
+ *
4
+ * Inspired by T3Code's orchestrator:
5
+ * - Events + projections + outbox effects committed atomically
6
+ * - Command acknowledgement = intent committed, not work finished
7
+ */
8
+ /**
9
+ * Event Sink - Atomic event sourcing
10
+ */
11
+ export class EventSink {
12
+ db = new Map();
13
+ sequence = 0;
14
+ onEventsCommitted;
15
+ constructor(options) {
16
+ this.onEventsCommitted = options.onEventsCommitted;
17
+ }
18
+ /**
19
+ * Commit events atomically with projections and outbox
20
+ */
21
+ async commit(events, outboxEffects = []) {
22
+ const committedEvents = [];
23
+ const outbox = [];
24
+ // Assign sequence numbers atomically
25
+ for (const event of events) {
26
+ this.sequence++;
27
+ const storedEvent = {
28
+ ...event,
29
+ sequence: this.sequence,
30
+ };
31
+ committedEvents.push(storedEvent);
32
+ // Store event
33
+ this.db.set(`event:${storedEvent.id}`, storedEvent);
34
+ }
35
+ // Create outbox entries
36
+ for (const effect of outboxEffects) {
37
+ const entry = {
38
+ id: crypto.randomUUID(),
39
+ eventId: committedEvents[0]?.id || '',
40
+ effect: effect.effect,
41
+ payload: effect.payload,
42
+ status: 'pending',
43
+ attempts: 0,
44
+ createdAt: new Date().toISOString(),
45
+ };
46
+ outbox.push(entry);
47
+ this.db.set(`outbox:${entry.id}`, entry);
48
+ }
49
+ // Notify listeners
50
+ this.onEventsCommitted?.(committedEvents);
51
+ return { events: committedEvents, outbox };
52
+ }
53
+ /**
54
+ * Get events by filter
55
+ */
56
+ async getEvents(filter) {
57
+ const events = [];
58
+ const limit = filter?.limit || 100;
59
+ for (const [key, value] of this.db.entries()) {
60
+ if (key.startsWith('event:') && typeof value === 'object') {
61
+ const event = value;
62
+ if (filter?.types && !filter.types.includes(event.type))
63
+ continue;
64
+ if (filter?.since && event.sequence <= filter.since)
65
+ continue;
66
+ if (filter?.until && event.sequence >= filter.until)
67
+ continue;
68
+ events.push(event);
69
+ if (events.length >= limit)
70
+ break;
71
+ }
72
+ }
73
+ return events.sort((a, b) => a.sequence - b.sequence);
74
+ }
75
+ /**
76
+ * Get pending outbox entries
77
+ */
78
+ async getPendingOutbox(limit = 10) {
79
+ const entries = [];
80
+ for (const [key, value] of this.db.entries()) {
81
+ if (key.startsWith('outbox:') && typeof value === 'object') {
82
+ const entry = value;
83
+ if (entry.status === 'pending' || entry.status === 'failed') {
84
+ entries.push(entry);
85
+ if (entries.length >= limit)
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ return entries;
91
+ }
92
+ /**
93
+ * Update outbox entry status
94
+ */
95
+ async updateOutboxStatus(id, status, error) {
96
+ const entry = this.db.get(`outbox:${id}`);
97
+ if (entry) {
98
+ entry.status = status;
99
+ entry.lastError = error;
100
+ entry.attempts++;
101
+ if (status === 'completed') {
102
+ entry.processedAt = new Date().toISOString();
103
+ }
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@pi-harness/event-sink",
3
+ "version": "1.1.337",
4
+ "type": "module",
5
+ "description": "Atomic event sourcing with SQLite transactions for pi-harness",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "bun test"
17
+ },
18
+ "dependencies": {
19
+ "@pi-harness/contracts": "workspace:*",
20
+ "better-sqlite3": "^11.7.0",
21
+ "uuid": "^11.0.3"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.7.2",
25
+ "@types/better-sqlite3": "^7.6.12",
26
+ "@types/uuid": "^10.0.0"
27
+ }
28
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Event Sink - Atomic Event Sourcing
3
+ *
4
+ * Inspired by T3Code's orchestrator event sourcing:
5
+ * - Commands serialize to event log
6
+ * - EventSink commits events + projections + outbox effects atomically
7
+ * - EffectWorker performs side effects AFTER intent commits
8
+ * - Command acknowledgement = intent committed, not work finished
9
+ */
10
+
11
+ import type { EventCreate } from '@pi-harness/contracts';
12
+
13
+ export { EventSink } from './sink.js';
14
+ export { ProjectionStore } from './projection.js';
15
+ export { OutboxWorker } from './outbox.js';
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Outbox Worker
3
+ *
4
+ * Processes outbox entries after events are committed.
5
+ * Side effects are performed asynchronously.
6
+ */
7
+
8
+ import type { OutboxEntry } from './sink.js';
9
+
10
+ /**
11
+ * Effect handler function
12
+ */
13
+ export type EffectHandler = (payload: unknown) => Promise<void>;
14
+
15
+ /**
16
+ * Outbox Worker options
17
+ */
18
+ export interface OutboxWorkerOptions {
19
+ /** Maximum retry attempts */
20
+ maxRetries?: number;
21
+ /** Retry delay in ms */
22
+ retryDelay?: number;
23
+ /** Effect handlers */
24
+ handlers?: Record<string, EffectHandler>;
25
+ }
26
+
27
+ /**
28
+ * Outbox Worker
29
+ *
30
+ * Processes pending outbox entries and executes their effects.
31
+ */
32
+ export class OutboxWorker {
33
+ private maxRetries: number;
34
+ private retryDelay: number;
35
+ private handlers: Map<string, EffectHandler> = new Map();
36
+ private running = false;
37
+ private interval?: ReturnType<typeof setInterval>;
38
+
39
+ constructor(options: OutboxWorkerOptions = {}) {
40
+ this.maxRetries = options.maxRetries ?? 3;
41
+ this.retryDelay = options.retryDelay ?? 1000;
42
+
43
+ if (options.handlers) {
44
+ for (const [name, handler] of Object.entries(options.handlers)) {
45
+ this.handlers.set(name, handler);
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Register an effect handler
52
+ */
53
+ register(effect: string, handler: EffectHandler): void {
54
+ this.handlers.set(effect, handler);
55
+ }
56
+
57
+ /**
58
+ * Start processing outbox
59
+ */
60
+ start(
61
+ getPending: () => Promise<OutboxEntry[]>,
62
+ updateStatus: (id: string, status: OutboxEntry['status'], error?: string) => Promise<void>,
63
+ pollInterval = 1000,
64
+ ): void {
65
+ if (this.running) return;
66
+ this.running = true;
67
+
68
+ this.interval = setInterval(async () => {
69
+ if (!this.running) return;
70
+
71
+ try {
72
+ const entries = await getPending();
73
+
74
+ for (const entry of entries) {
75
+ if (!this.running) break;
76
+
77
+ if (entry.attempts >= this.maxRetries) {
78
+ await updateStatus(entry.id, 'failed', 'Max retries exceeded');
79
+ continue;
80
+ }
81
+
82
+ const handler = this.handlers.get(entry.effect);
83
+ if (!handler) {
84
+ await updateStatus(entry.id, 'failed', `No handler for effect: ${entry.effect}`);
85
+ continue;
86
+ }
87
+
88
+ try {
89
+ await updateStatus(entry.id, 'processing');
90
+ await handler(entry.payload);
91
+ await updateStatus(entry.id, 'completed');
92
+ } catch (error) {
93
+ await updateStatus(entry.id, 'failed', String(error));
94
+ }
95
+ }
96
+ } catch (error) {
97
+ console.error('[OutboxWorker] Error processing outbox:', error);
98
+ }
99
+ }, pollInterval);
100
+ }
101
+
102
+ /**
103
+ * Stop processing
104
+ */
105
+ stop(): void {
106
+ this.running = false;
107
+ if (this.interval) {
108
+ clearInterval(this.interval);
109
+ this.interval = undefined;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Check if running
115
+ */
116
+ isRunning(): boolean {
117
+ return this.running;
118
+ }
119
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Projection Store
3
+ *
4
+ * Maintains materialized views of events for efficient queries.
5
+ * Projections are updated atomically with events.
6
+ */
7
+
8
+ import type { StoredEvent } from './sink.js';
9
+
10
+ /**
11
+ * Projection function type
12
+ */
13
+ export type ProjectionFn<T> = (state: T | null, event: StoredEvent) => T;
14
+
15
+ /**
16
+ * Internal projection entry with state
17
+ */
18
+ interface ProjectionEntry<T> {
19
+ name: string;
20
+ state: T;
21
+ initialState: T;
22
+ reduce: ProjectionFn<T>;
23
+ }
24
+
25
+ /**
26
+ * Projection Store
27
+ */
28
+ export class ProjectionStore {
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ private projections = new Map<string, ProjectionEntry<any>>();
31
+
32
+ /**
33
+ * Register a projection
34
+ */
35
+ register<T>(projection: { name: string; initialState: T; reduce: ProjectionFn<T> }): void {
36
+ this.projections.set(projection.name, {
37
+ name: projection.name,
38
+ state: projection.initialState,
39
+ initialState: projection.initialState,
40
+ reduce: projection.reduce,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Apply events to all projections
46
+ */
47
+ apply(events: StoredEvent[]): void {
48
+ for (const event of events) {
49
+ for (const [name, entry] of this.projections.entries()) {
50
+ try {
51
+ entry.state = entry.reduce(entry.state, event);
52
+ } catch {
53
+ // Silently ignore projection errors - don't crash the event loop
54
+ // callers can check projection state after apply()
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Get projection state
62
+ */
63
+ get<T>(name: string): T | null {
64
+ const entry = this.projections.get(name);
65
+ return entry ? entry.state : null;
66
+ }
67
+
68
+ /**
69
+ * Reset projection to initial state
70
+ */
71
+ reset(name: string): void {
72
+ const entry = this.projections.get(name);
73
+ if (entry) {
74
+ entry.state = entry.initialState;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * List all projection names
80
+ */
81
+ list(): string[] {
82
+ return Array.from(this.projections.keys());
83
+ }
84
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Event Sink - Atomic Event Sourcing
3
+ *
4
+ * Inspired by T3Code's orchestrator:
5
+ * - Events + projections + outbox effects committed atomically
6
+ * - Command acknowledgement = intent committed, not work finished
7
+ */
8
+
9
+ /**
10
+ * Event with metadata
11
+ */
12
+ export interface StoredEvent {
13
+ id: string;
14
+ type: string;
15
+ timestamp: string;
16
+ workspaceId?: string;
17
+ sessionId?: string;
18
+ jobId?: string;
19
+ payload?: Record<string, unknown>;
20
+ metadata?: Record<string, unknown>;
21
+ commandId?: string;
22
+ sequence: number;
23
+ }
24
+
25
+ /**
26
+ * Outbox entry for side effects
27
+ */
28
+ export interface OutboxEntry {
29
+ id: string;
30
+ eventId: string;
31
+ effect: string;
32
+ payload: unknown;
33
+ status: 'pending' | 'processing' | 'completed' | 'failed';
34
+ attempts: number;
35
+ lastError?: string;
36
+ createdAt: string;
37
+ processedAt?: string;
38
+ }
39
+
40
+ /**
41
+ * Event Sink options
42
+ */
43
+ export interface EventSinkOptions {
44
+ /** Database file path */
45
+ dbPath: string;
46
+ /** Called when events are committed */
47
+ onEventsCommitted?: (events: StoredEvent[]) => void;
48
+ }
49
+
50
+ /**
51
+ * Event Sink - Atomic event sourcing
52
+ */
53
+ export class EventSink {
54
+ private db: Map<string, unknown> = new Map();
55
+ private sequence = 0;
56
+ private onEventsCommitted?: (events: StoredEvent[]) => void;
57
+
58
+ constructor(options: EventSinkOptions) {
59
+ this.onEventsCommitted = options.onEventsCommitted;
60
+ }
61
+
62
+ /**
63
+ * Commit events atomically with projections and outbox
64
+ */
65
+ async commit<T extends StoredEvent>(
66
+ events: T[],
67
+ outboxEffects: Array<{ effect: string; payload: unknown }> = [],
68
+ ): Promise<{ events: StoredEvent[]; outbox: OutboxEntry[] }> {
69
+ const committedEvents: StoredEvent[] = [];
70
+ const outbox: OutboxEntry[] = [];
71
+
72
+ // Assign sequence numbers atomically
73
+ for (const event of events) {
74
+ this.sequence++;
75
+ const storedEvent: StoredEvent = {
76
+ ...event,
77
+ sequence: this.sequence,
78
+ };
79
+ committedEvents.push(storedEvent);
80
+
81
+ // Store event
82
+ this.db.set(`event:${storedEvent.id}`, storedEvent);
83
+ }
84
+
85
+ // Create outbox entries
86
+ for (const effect of outboxEffects) {
87
+ const entry: OutboxEntry = {
88
+ id: crypto.randomUUID(),
89
+ eventId: committedEvents[0]?.id || '',
90
+ effect: effect.effect,
91
+ payload: effect.payload,
92
+ status: 'pending',
93
+ attempts: 0,
94
+ createdAt: new Date().toISOString(),
95
+ };
96
+ outbox.push(entry);
97
+ this.db.set(`outbox:${entry.id}`, entry);
98
+ }
99
+
100
+ // Notify listeners
101
+ this.onEventsCommitted?.(committedEvents);
102
+
103
+ return { events: committedEvents, outbox };
104
+ }
105
+
106
+ /**
107
+ * Get events by filter
108
+ */
109
+ async getEvents(filter?: {
110
+ types?: string[];
111
+ since?: number;
112
+ until?: number;
113
+ limit?: number;
114
+ }): Promise<StoredEvent[]> {
115
+ const events: StoredEvent[] = [];
116
+ const limit = filter?.limit || 100;
117
+
118
+ for (const [key, value] of this.db.entries()) {
119
+ if (key.startsWith('event:') && typeof value === 'object') {
120
+ const event = value as StoredEvent;
121
+
122
+ if (filter?.types && !filter.types.includes(event.type)) continue;
123
+ if (filter?.since && event.sequence <= filter.since) continue;
124
+ if (filter?.until && event.sequence >= filter.until) continue;
125
+
126
+ events.push(event);
127
+ if (events.length >= limit) break;
128
+ }
129
+ }
130
+
131
+ return events.sort((a, b) => a.sequence - b.sequence);
132
+ }
133
+
134
+ /**
135
+ * Get pending outbox entries
136
+ */
137
+ async getPendingOutbox(limit = 10): Promise<OutboxEntry[]> {
138
+ const entries: OutboxEntry[] = [];
139
+
140
+ for (const [key, value] of this.db.entries()) {
141
+ if (key.startsWith('outbox:') && typeof value === 'object') {
142
+ const entry = value as OutboxEntry;
143
+ if (entry.status === 'pending' || entry.status === 'failed') {
144
+ entries.push(entry);
145
+ if (entries.length >= limit) break;
146
+ }
147
+ }
148
+ }
149
+
150
+ return entries;
151
+ }
152
+
153
+ /**
154
+ * Update outbox entry status
155
+ */
156
+ async updateOutboxStatus(id: string, status: OutboxEntry['status'], error?: string): Promise<void> {
157
+ const entry = this.db.get(`outbox:${id}`) as OutboxEntry | undefined;
158
+ if (entry) {
159
+ entry.status = status;
160
+ entry.lastError = error;
161
+ entry.attempts++;
162
+ if (status === 'completed') {
163
+ entry.processedAt = new Date().toISOString();
164
+ }
165
+ }
166
+ }
167
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/event-store",
3
- "version": "1.1.335",
3
+ "version": "1.1.337",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/experience-replay",
3
- "version": "1.1.335",
3
+ "version": "1.1.337",
4
4
  "description": "Experience replay for debugging and reproducibility (RFC-0059)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/feedback-collector",
3
- "version": "1.1.335",
3
+ "version": "1.1.337",
4
4
  "type": "module",
5
5
  "description": "Collect and aggregate human/automated feedback (RFC-0021)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/file-copy-helper",
3
- "version": "1.1.335",
3
+ "version": "1.1.337",
4
4
  "description": "Injects cp/sudo cp rule when user asks to mimic/copy files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",