@trevonistrevon/pi-loop 0.4.10 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/coordinator.d.ts +35 -0
- package/dist/coordinator.js +56 -0
- package/dist/goal-types.d.ts +78 -0
- package/dist/goal-types.js +1 -0
- package/dist/goal-verifier.d.ts +20 -0
- package/dist/goal-verifier.js +198 -0
- package/dist/index.js +182 -40
- package/dist/loop-reducer.d.ts +63 -0
- package/dist/loop-reducer.js +67 -0
- package/dist/monitor-completion-coordinator.d.ts +10 -0
- package/dist/monitor-completion-coordinator.js +13 -0
- package/dist/monitor-manager.d.ts +2 -0
- package/dist/monitor-manager.js +107 -29
- package/dist/monitor-reducer.d.ts +82 -0
- package/dist/monitor-reducer.js +69 -0
- package/dist/notification-reducer.d.ts +81 -0
- package/dist/notification-reducer.js +65 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +118 -44
- package/dist/task-backlog-coordinator.d.ts +12 -0
- package/dist/task-backlog-coordinator.js +22 -0
- package/dist/task-reducer.d.ts +66 -0
- package/dist/task-reducer.js +76 -0
- package/dist/task-store.d.ts +2 -0
- package/dist/task-store.js +82 -30
- package/docs/architecture/goal-state-schema.md +505 -0
- package/docs/architecture/state-machine-migration.md +546 -0
- package/docs/architecture/state-machine-reducer-event-model.md +823 -0
- package/docs/architecture/state-machine-test-matrix.md +249 -0
- package/docs/architecture/state-machine-transition-map.md +436 -0
- package/package.json +1 -1
- package/src/coordinator.ts +115 -0
- package/src/goal-types.ts +99 -0
- package/src/goal-verifier.ts +241 -0
- package/src/index.ts +209 -39
- package/src/loop-reducer.ts +148 -0
- package/src/monitor-completion-coordinator.ts +24 -0
- package/src/monitor-manager.ts +115 -27
- package/src/monitor-reducer.ts +166 -0
- package/src/notification-reducer.ts +155 -0
- package/src/store.ts +119 -43
- package/src/task-backlog-coordinator.ts +32 -0
- package/src/task-reducer.ts +152 -0
- package/src/task-store.ts +84 -27
package/dist/monitor-manager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { reduceMonitorState, } from "./monitor-reducer.js";
|
|
2
3
|
export class MonitorManager {
|
|
3
4
|
pi;
|
|
4
5
|
processes = new Map();
|
|
@@ -6,18 +7,41 @@ export class MonitorManager {
|
|
|
6
7
|
constructor(pi) {
|
|
7
8
|
this.pi = pi;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
id,
|
|
13
|
-
command,
|
|
14
|
-
description,
|
|
15
|
-
timeout,
|
|
16
|
-
status: "running",
|
|
17
|
-
startedAt: Date.now(),
|
|
18
|
-
outputLines: 0,
|
|
19
|
-
outputBuffer: [],
|
|
10
|
+
toReducerState() {
|
|
11
|
+
return {
|
|
12
|
+
nextId: this.nextId,
|
|
13
|
+
monitorsById: Object.fromEntries(Array.from(this.processes.entries()).map(([id, process]) => [id, process.entry])),
|
|
20
14
|
};
|
|
15
|
+
}
|
|
16
|
+
applyReducerEvent(event) {
|
|
17
|
+
const result = reduceMonitorState(this.toReducerState(), event);
|
|
18
|
+
this.nextId = result.state.nextId;
|
|
19
|
+
for (const [id, process] of [...this.processes.entries()]) {
|
|
20
|
+
const updated = result.state.monitorsById[id];
|
|
21
|
+
if (!updated) {
|
|
22
|
+
this.processes.delete(id);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
process.entry = updated;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
create(command, description, timeout = 300000) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const result = reduceMonitorState(this.toReducerState(), {
|
|
32
|
+
type: "MONITOR_CREATED",
|
|
33
|
+
at: now,
|
|
34
|
+
source: "tool",
|
|
35
|
+
entityType: "monitor",
|
|
36
|
+
payload: {
|
|
37
|
+
command,
|
|
38
|
+
description,
|
|
39
|
+
timeout,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
this.nextId = result.state.nextId;
|
|
43
|
+
const id = String(this.nextId - 1);
|
|
44
|
+
const entry = result.state.monitorsById[id];
|
|
21
45
|
const abortController = new AbortController();
|
|
22
46
|
const child = spawn("sh", ["-c", command], {
|
|
23
47
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -37,9 +61,14 @@ export class MonitorManager {
|
|
|
37
61
|
for (const line of lines) {
|
|
38
62
|
if (line.length === 0)
|
|
39
63
|
continue;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
this.applyReducerEvent({
|
|
65
|
+
type: "MONITOR_OUTPUT",
|
|
66
|
+
at: Date.now(),
|
|
67
|
+
source: "monitor",
|
|
68
|
+
entityType: "monitor",
|
|
69
|
+
entityId: id,
|
|
70
|
+
payload: { id, line },
|
|
71
|
+
});
|
|
43
72
|
this.pi.events.emit("monitor:output", {
|
|
44
73
|
monitorId: id,
|
|
45
74
|
line,
|
|
@@ -52,9 +81,14 @@ export class MonitorManager {
|
|
|
52
81
|
for (const line of lines) {
|
|
53
82
|
if (line.length === 0)
|
|
54
83
|
continue;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
this.applyReducerEvent({
|
|
85
|
+
type: "MONITOR_OUTPUT",
|
|
86
|
+
at: Date.now(),
|
|
87
|
+
source: "monitor",
|
|
88
|
+
entityType: "monitor",
|
|
89
|
+
entityId: id,
|
|
90
|
+
payload: { id, line },
|
|
91
|
+
});
|
|
58
92
|
this.pi.events.emit("monitor:output", {
|
|
59
93
|
monitorId: id,
|
|
60
94
|
line,
|
|
@@ -63,13 +97,22 @@ export class MonitorManager {
|
|
|
63
97
|
}
|
|
64
98
|
});
|
|
65
99
|
const finish = (code, status) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
this.applyReducerEvent({
|
|
101
|
+
type: status === "completed" ? "MONITOR_COMPLETED" : "MONITOR_ERRORED",
|
|
102
|
+
at: Date.now(),
|
|
103
|
+
source: "monitor",
|
|
104
|
+
entityType: "monitor",
|
|
105
|
+
entityId: id,
|
|
106
|
+
payload: {
|
|
107
|
+
id,
|
|
108
|
+
exitCode: code ?? undefined,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const current = this.get(id);
|
|
69
112
|
this.pi.events.emit(status === "completed" ? "monitor:done" : "monitor:error", {
|
|
70
113
|
monitorId: id,
|
|
71
114
|
exitCode: code,
|
|
72
|
-
outputLines:
|
|
115
|
+
outputLines: current.outputLines,
|
|
73
116
|
});
|
|
74
117
|
if (status === "completed") {
|
|
75
118
|
for (const callback of bp.completionCallbacks)
|
|
@@ -81,17 +124,35 @@ export class MonitorManager {
|
|
|
81
124
|
bp.waiters = [];
|
|
82
125
|
// Remove completed/errored monitors after a brief delay so tool
|
|
83
126
|
// consumers have time to read the final state via MonitorList.
|
|
84
|
-
setTimeout(() => {
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
this.applyReducerEvent({
|
|
129
|
+
type: "MONITOR_PRUNED",
|
|
130
|
+
at: Date.now(),
|
|
131
|
+
source: "system",
|
|
132
|
+
entityType: "monitor",
|
|
133
|
+
entityId: id,
|
|
134
|
+
payload: { id },
|
|
135
|
+
});
|
|
136
|
+
}, 30000);
|
|
85
137
|
};
|
|
86
138
|
child.on("close", (code) => {
|
|
87
|
-
if (entry.status === "running") {
|
|
139
|
+
if (bp.entry.status === "running") {
|
|
88
140
|
finish(code, code === 0 ? "completed" : "error");
|
|
89
141
|
}
|
|
90
142
|
});
|
|
91
143
|
child.on("error", (err) => {
|
|
92
|
-
if (entry.status === "running") {
|
|
93
|
-
|
|
94
|
-
|
|
144
|
+
if (bp.entry.status === "running") {
|
|
145
|
+
this.applyReducerEvent({
|
|
146
|
+
type: "MONITOR_ERRORED",
|
|
147
|
+
at: Date.now(),
|
|
148
|
+
source: "monitor",
|
|
149
|
+
entityType: "monitor",
|
|
150
|
+
entityId: id,
|
|
151
|
+
payload: {
|
|
152
|
+
id,
|
|
153
|
+
error: err.message,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
95
156
|
this.pi.events.emit("monitor:error", {
|
|
96
157
|
monitorId: id,
|
|
97
158
|
error: err.message,
|
|
@@ -104,7 +165,7 @@ export class MonitorManager {
|
|
|
104
165
|
});
|
|
105
166
|
if (timeout > 0) {
|
|
106
167
|
setTimeout(() => {
|
|
107
|
-
if (entry.status === "running") {
|
|
168
|
+
if (bp.entry.status === "running") {
|
|
108
169
|
this.stop(id);
|
|
109
170
|
}
|
|
110
171
|
}, timeout);
|
|
@@ -125,7 +186,17 @@ export class MonitorManager {
|
|
|
125
186
|
const bp = this.processes.get(id);
|
|
126
187
|
if (!bp || bp.entry.status !== "running")
|
|
127
188
|
return false;
|
|
128
|
-
|
|
189
|
+
this.applyReducerEvent({
|
|
190
|
+
type: "MONITOR_STOPPED",
|
|
191
|
+
at: Date.now(),
|
|
192
|
+
source: "tool",
|
|
193
|
+
entityType: "monitor",
|
|
194
|
+
entityId: id,
|
|
195
|
+
payload: {
|
|
196
|
+
id,
|
|
197
|
+
reason: "manual",
|
|
198
|
+
},
|
|
199
|
+
});
|
|
129
200
|
bp.proc.kill("SIGTERM");
|
|
130
201
|
await new Promise((resolve) => {
|
|
131
202
|
const timer = setTimeout(() => {
|
|
@@ -140,7 +211,6 @@ export class MonitorManager {
|
|
|
140
211
|
resolve();
|
|
141
212
|
});
|
|
142
213
|
});
|
|
143
|
-
bp.entry.completedAt = Date.now();
|
|
144
214
|
bp.completionCallbacks = [];
|
|
145
215
|
for (const resolve of bp.waiters)
|
|
146
216
|
resolve();
|
|
@@ -157,6 +227,14 @@ export class MonitorManager {
|
|
|
157
227
|
}
|
|
158
228
|
if (bp.entry.status !== "running")
|
|
159
229
|
return false;
|
|
230
|
+
this.applyReducerEvent({
|
|
231
|
+
type: "MONITOR_ONDONE_REGISTERED",
|
|
232
|
+
at: Date.now(),
|
|
233
|
+
source: "tool",
|
|
234
|
+
entityType: "monitor",
|
|
235
|
+
entityId: id,
|
|
236
|
+
payload: { id },
|
|
237
|
+
});
|
|
160
238
|
bp.completionCallbacks.push(callback);
|
|
161
239
|
return true;
|
|
162
240
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { MonitorEntry } from "./types.js";
|
|
2
|
+
type ReducerSource = "tool" | "command" | "scheduler" | "eventbus" | "monitor" | "session" | "coordinator" | "system";
|
|
3
|
+
export interface MonitorReducerEntry extends MonitorEntry {
|
|
4
|
+
onDoneRegistered?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface MonitorReducerState {
|
|
7
|
+
nextId: number;
|
|
8
|
+
monitorsById: Record<string, MonitorReducerEntry>;
|
|
9
|
+
}
|
|
10
|
+
export type MonitorReducerEvent = {
|
|
11
|
+
type: "MONITOR_CREATED";
|
|
12
|
+
at: number;
|
|
13
|
+
source: ReducerSource;
|
|
14
|
+
entityType?: "monitor";
|
|
15
|
+
entityId?: string;
|
|
16
|
+
payload: {
|
|
17
|
+
command: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
timeout: number;
|
|
20
|
+
};
|
|
21
|
+
} | {
|
|
22
|
+
type: "MONITOR_OUTPUT";
|
|
23
|
+
at: number;
|
|
24
|
+
source: ReducerSource;
|
|
25
|
+
entityType?: "monitor";
|
|
26
|
+
entityId?: string;
|
|
27
|
+
payload: {
|
|
28
|
+
id: string;
|
|
29
|
+
line: string;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
type: "MONITOR_COMPLETED" | "MONITOR_ERRORED";
|
|
33
|
+
at: number;
|
|
34
|
+
source: ReducerSource;
|
|
35
|
+
entityType?: "monitor";
|
|
36
|
+
entityId?: string;
|
|
37
|
+
payload: {
|
|
38
|
+
id: string;
|
|
39
|
+
exitCode?: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
};
|
|
42
|
+
} | {
|
|
43
|
+
type: "MONITOR_STOPPED";
|
|
44
|
+
at: number;
|
|
45
|
+
source: ReducerSource;
|
|
46
|
+
entityType?: "monitor";
|
|
47
|
+
entityId?: string;
|
|
48
|
+
payload: {
|
|
49
|
+
id: string;
|
|
50
|
+
reason: "manual" | "timeout";
|
|
51
|
+
};
|
|
52
|
+
} | {
|
|
53
|
+
type: "MONITOR_PRUNED" | "MONITOR_ONDONE_REGISTERED";
|
|
54
|
+
at: number;
|
|
55
|
+
source: ReducerSource;
|
|
56
|
+
entityType?: "monitor";
|
|
57
|
+
entityId?: string;
|
|
58
|
+
payload: {
|
|
59
|
+
id: string;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type MonitorReducerEffect = {
|
|
63
|
+
type: "PERSIST_MONITOR";
|
|
64
|
+
entityType: "monitor";
|
|
65
|
+
entityId: string;
|
|
66
|
+
payload: {
|
|
67
|
+
monitor: MonitorReducerEntry;
|
|
68
|
+
};
|
|
69
|
+
} | {
|
|
70
|
+
type: "DELETE_MONITOR";
|
|
71
|
+
entityType: "monitor";
|
|
72
|
+
entityId: string;
|
|
73
|
+
payload: {
|
|
74
|
+
id: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export interface MonitorReduceResult {
|
|
78
|
+
state: MonitorReducerState;
|
|
79
|
+
effects: MonitorReducerEffect[];
|
|
80
|
+
}
|
|
81
|
+
export declare function reduceMonitorState(state: MonitorReducerState, event: MonitorReducerEvent): MonitorReduceResult;
|
|
82
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function cloneState(state) {
|
|
2
|
+
return {
|
|
3
|
+
nextId: state.nextId,
|
|
4
|
+
monitorsById: { ...state.monitorsById },
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function reduceMonitorState(state, event) {
|
|
8
|
+
if (event.type === "MONITOR_CREATED") {
|
|
9
|
+
const next = cloneState(state);
|
|
10
|
+
const id = String(next.nextId++);
|
|
11
|
+
const monitor = {
|
|
12
|
+
id,
|
|
13
|
+
command: event.payload.command,
|
|
14
|
+
description: event.payload.description,
|
|
15
|
+
timeout: event.payload.timeout,
|
|
16
|
+
status: "running",
|
|
17
|
+
startedAt: event.at,
|
|
18
|
+
outputLines: 0,
|
|
19
|
+
outputBuffer: [],
|
|
20
|
+
};
|
|
21
|
+
next.monitorsById[id] = monitor;
|
|
22
|
+
return {
|
|
23
|
+
state: next,
|
|
24
|
+
effects: [{ type: "PERSIST_MONITOR", entityType: "monitor", entityId: id, payload: { monitor } }],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const id = event.payload.id;
|
|
28
|
+
const current = state.monitorsById[id];
|
|
29
|
+
if (!current)
|
|
30
|
+
return { state, effects: [] };
|
|
31
|
+
if (event.type === "MONITOR_PRUNED") {
|
|
32
|
+
const next = cloneState(state);
|
|
33
|
+
delete next.monitorsById[id];
|
|
34
|
+
return {
|
|
35
|
+
state: next,
|
|
36
|
+
effects: [{ type: "DELETE_MONITOR", entityType: "monitor", entityId: id, payload: { id } }],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const next = cloneState(state);
|
|
40
|
+
const monitor = { ...current };
|
|
41
|
+
if (event.type === "MONITOR_OUTPUT") {
|
|
42
|
+
monitor.outputLines++;
|
|
43
|
+
if (monitor.outputBuffer.length < 200)
|
|
44
|
+
monitor.outputBuffer = [...monitor.outputBuffer, event.payload.line];
|
|
45
|
+
}
|
|
46
|
+
if (event.type === "MONITOR_COMPLETED") {
|
|
47
|
+
monitor.status = "completed";
|
|
48
|
+
monitor.exitCode = event.payload.exitCode;
|
|
49
|
+
monitor.completedAt = event.at;
|
|
50
|
+
}
|
|
51
|
+
if (event.type === "MONITOR_ERRORED") {
|
|
52
|
+
monitor.status = "error";
|
|
53
|
+
if (event.payload.exitCode !== undefined)
|
|
54
|
+
monitor.exitCode = event.payload.exitCode;
|
|
55
|
+
monitor.completedAt = event.at;
|
|
56
|
+
}
|
|
57
|
+
if (event.type === "MONITOR_STOPPED") {
|
|
58
|
+
monitor.status = "stopped";
|
|
59
|
+
monitor.completedAt = event.at;
|
|
60
|
+
}
|
|
61
|
+
if (event.type === "MONITOR_ONDONE_REGISTERED") {
|
|
62
|
+
monitor.onDoneRegistered = true;
|
|
63
|
+
}
|
|
64
|
+
next.monitorsById[id] = monitor;
|
|
65
|
+
return {
|
|
66
|
+
state: next,
|
|
67
|
+
effects: [{ type: "PERSIST_MONITOR", entityType: "monitor", entityId: id, payload: { monitor } }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
type ReducerSource = "tool" | "command" | "scheduler" | "eventbus" | "monitor" | "session" | "coordinator" | "system";
|
|
2
|
+
export interface ReducerNotification {
|
|
3
|
+
key: string;
|
|
4
|
+
loopId: string;
|
|
5
|
+
message: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
trigger: unknown;
|
|
8
|
+
recurring?: boolean;
|
|
9
|
+
autoTask?: boolean;
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface NotificationReducerState {
|
|
13
|
+
notificationsByKey: Record<string, ReducerNotification>;
|
|
14
|
+
agentRunning: boolean;
|
|
15
|
+
hasPendingMessages: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type NotificationReducerEvent = {
|
|
18
|
+
type: "NOTIFICATION_QUEUED";
|
|
19
|
+
at: number;
|
|
20
|
+
source: ReducerSource;
|
|
21
|
+
entityType?: "notification";
|
|
22
|
+
entityId?: string;
|
|
23
|
+
payload: {
|
|
24
|
+
notification: ReducerNotification;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
type: "NOTIFICATION_DROPPED";
|
|
28
|
+
at: number;
|
|
29
|
+
source: ReducerSource;
|
|
30
|
+
entityType?: "notification";
|
|
31
|
+
entityId?: string;
|
|
32
|
+
payload: {
|
|
33
|
+
key: string;
|
|
34
|
+
reason: "zero_pending_tasks" | "session_switch" | "session_shutdown" | "superseded";
|
|
35
|
+
};
|
|
36
|
+
} | {
|
|
37
|
+
type: "NOTIFICATION_CLEARED";
|
|
38
|
+
at: number;
|
|
39
|
+
source: ReducerSource;
|
|
40
|
+
entityType?: "notification";
|
|
41
|
+
entityId?: string;
|
|
42
|
+
payload: {
|
|
43
|
+
reason: "session_switch" | "session_shutdown";
|
|
44
|
+
};
|
|
45
|
+
} | {
|
|
46
|
+
type: "NOTIFICATION_FLUSH_REQUESTED";
|
|
47
|
+
at: number;
|
|
48
|
+
source: ReducerSource;
|
|
49
|
+
entityType?: "notification";
|
|
50
|
+
entityId?: string;
|
|
51
|
+
payload: {
|
|
52
|
+
ignorePendingMessages?: boolean;
|
|
53
|
+
};
|
|
54
|
+
} | {
|
|
55
|
+
type: "NOTIFICATION_RUNTIME_UPDATED";
|
|
56
|
+
at: number;
|
|
57
|
+
source: ReducerSource;
|
|
58
|
+
entityType?: "notification";
|
|
59
|
+
entityId?: string;
|
|
60
|
+
payload: {
|
|
61
|
+
agentRunning: boolean;
|
|
62
|
+
hasPendingMessages: boolean;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export type NotificationReducerEffect = {
|
|
66
|
+
type: "REQUEST_NOTIFICATION_FLUSH";
|
|
67
|
+
payload: Record<string, never>;
|
|
68
|
+
} | {
|
|
69
|
+
type: "DELIVER_NOTIFICATION";
|
|
70
|
+
entityType: "notification";
|
|
71
|
+
entityId: string;
|
|
72
|
+
payload: {
|
|
73
|
+
notification: ReducerNotification;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export interface NotificationReduceResult {
|
|
77
|
+
state: NotificationReducerState;
|
|
78
|
+
effects: NotificationReducerEffect[];
|
|
79
|
+
}
|
|
80
|
+
export declare function reduceNotificationState(state: NotificationReducerState, event: NotificationReducerEvent): NotificationReduceResult;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function cloneState(state) {
|
|
2
|
+
return {
|
|
3
|
+
notificationsByKey: { ...state.notificationsByKey },
|
|
4
|
+
agentRunning: state.agentRunning,
|
|
5
|
+
hasPendingMessages: state.hasPendingMessages,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function reduceNotificationState(state, event) {
|
|
9
|
+
if (event.type === "NOTIFICATION_QUEUED") {
|
|
10
|
+
const next = cloneState(state);
|
|
11
|
+
next.notificationsByKey[event.payload.notification.key] = event.payload.notification;
|
|
12
|
+
return {
|
|
13
|
+
state: next,
|
|
14
|
+
effects: [{ type: "REQUEST_NOTIFICATION_FLUSH", payload: {} }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (event.type === "NOTIFICATION_DROPPED") {
|
|
18
|
+
const next = cloneState(state);
|
|
19
|
+
delete next.notificationsByKey[event.payload.key];
|
|
20
|
+
return { state: next, effects: [] };
|
|
21
|
+
}
|
|
22
|
+
if (event.type === "NOTIFICATION_CLEARED") {
|
|
23
|
+
return {
|
|
24
|
+
state: {
|
|
25
|
+
...state,
|
|
26
|
+
notificationsByKey: {},
|
|
27
|
+
},
|
|
28
|
+
effects: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (event.type === "NOTIFICATION_RUNTIME_UPDATED") {
|
|
32
|
+
return {
|
|
33
|
+
state: {
|
|
34
|
+
...state,
|
|
35
|
+
agentRunning: event.payload.agentRunning,
|
|
36
|
+
hasPendingMessages: event.payload.hasPendingMessages,
|
|
37
|
+
},
|
|
38
|
+
effects: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (event.type === "NOTIFICATION_FLUSH_REQUESTED") {
|
|
42
|
+
if (state.agentRunning)
|
|
43
|
+
return { state, effects: [] };
|
|
44
|
+
if (!event.payload.ignorePendingMessages && state.hasPendingMessages) {
|
|
45
|
+
return { state, effects: [] };
|
|
46
|
+
}
|
|
47
|
+
const queued = Object.values(state.notificationsByKey)
|
|
48
|
+
.sort((left, right) => left.timestamp - right.timestamp);
|
|
49
|
+
const nextNotification = queued[0];
|
|
50
|
+
if (!nextNotification)
|
|
51
|
+
return { state, effects: [] };
|
|
52
|
+
const next = cloneState(state);
|
|
53
|
+
delete next.notificationsByKey[nextNotification.key];
|
|
54
|
+
return {
|
|
55
|
+
state: next,
|
|
56
|
+
effects: [{
|
|
57
|
+
type: "DELIVER_NOTIFICATION",
|
|
58
|
+
entityType: "notification",
|
|
59
|
+
entityId: nextNotification.key,
|
|
60
|
+
payload: { notification: nextNotification },
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { state, effects: [] };
|
|
65
|
+
}
|
package/dist/store.d.ts
CHANGED