@springbrand/agent-runtime 0.2.0-alpha.22 → 0.2.0-alpha.24
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/package.json +1 -1
- package/src/db/runtime-event-outbox.repo.ts +39 -8
- package/src/db/schema.ts +18 -1
- package/src/runtime.ts +22 -5
package/package.json
CHANGED
|
@@ -5,6 +5,9 @@ export interface StoredRuntimeEvent {
|
|
|
5
5
|
body: string;
|
|
6
6
|
createdAt: number;
|
|
7
7
|
deliveredAt: number | null;
|
|
8
|
+
attemptCount: number;
|
|
9
|
+
quarantinedAt: number | null;
|
|
10
|
+
lastError: string | null;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export interface NewRuntimeEvent {
|
|
@@ -18,6 +21,9 @@ type RuntimeEventRow = {
|
|
|
18
21
|
body: string;
|
|
19
22
|
created_at: number;
|
|
20
23
|
delivered_at: number | null;
|
|
24
|
+
attempt_count: number;
|
|
25
|
+
quarantined_at: number | null;
|
|
26
|
+
last_error: string | null;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
function mapRow(row: RuntimeEventRow): StoredRuntimeEvent {
|
|
@@ -26,6 +32,9 @@ function mapRow(row: RuntimeEventRow): StoredRuntimeEvent {
|
|
|
26
32
|
body: row.body,
|
|
27
33
|
createdAt: row.created_at,
|
|
28
34
|
deliveredAt: row.delivered_at,
|
|
35
|
+
attemptCount: row.attempt_count,
|
|
36
|
+
quarantinedAt: row.quarantined_at,
|
|
37
|
+
lastError: row.last_error,
|
|
29
38
|
};
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -42,9 +51,11 @@ export class RuntimeEventOutboxRepository {
|
|
|
42
51
|
}
|
|
43
52
|
this.sql`
|
|
44
53
|
INSERT INTO pi_turn_event_outbox (
|
|
45
|
-
event_id, body, created_at, delivered_at
|
|
54
|
+
event_id, body, created_at, delivered_at,
|
|
55
|
+
attempt_count, quarantined_at, last_error
|
|
46
56
|
) VALUES (
|
|
47
|
-
${event.eventId}, ${event.body}, ${event.createdAt}, NULL
|
|
57
|
+
${event.eventId}, ${event.body}, ${event.createdAt}, NULL,
|
|
58
|
+
0, NULL, NULL
|
|
48
59
|
)
|
|
49
60
|
`;
|
|
50
61
|
return true;
|
|
@@ -52,9 +63,10 @@ export class RuntimeEventOutboxRepository {
|
|
|
52
63
|
|
|
53
64
|
listPending(): StoredRuntimeEvent[] {
|
|
54
65
|
return this.sql<RuntimeEventRow>`
|
|
55
|
-
SELECT event_id, body, created_at, delivered_at
|
|
66
|
+
SELECT event_id, body, created_at, delivered_at,
|
|
67
|
+
attempt_count, quarantined_at, last_error
|
|
56
68
|
FROM pi_turn_event_outbox
|
|
57
|
-
WHERE delivered_at IS NULL
|
|
69
|
+
WHERE delivered_at IS NULL AND quarantined_at IS NULL
|
|
58
70
|
ORDER BY created_at, rowid
|
|
59
71
|
`.map(mapRow);
|
|
60
72
|
}
|
|
@@ -64,7 +76,7 @@ export class RuntimeEventOutboxRepository {
|
|
|
64
76
|
this.sql<{ pending: number }>`
|
|
65
77
|
SELECT 1 AS pending
|
|
66
78
|
FROM pi_turn_event_outbox
|
|
67
|
-
WHERE delivered_at IS NULL
|
|
79
|
+
WHERE delivered_at IS NULL AND quarantined_at IS NULL
|
|
68
80
|
LIMIT 1
|
|
69
81
|
`[0],
|
|
70
82
|
);
|
|
@@ -106,14 +118,33 @@ export class RuntimeEventOutboxRepository {
|
|
|
106
118
|
markDelivered(eventId: string, deliveredAt: number): void {
|
|
107
119
|
this.sql`
|
|
108
120
|
UPDATE pi_turn_event_outbox
|
|
109
|
-
SET delivered_at = ${deliveredAt}
|
|
121
|
+
SET delivered_at = ${deliveredAt}, last_error = NULL
|
|
110
122
|
WHERE event_id = ${eventId} AND delivered_at IS NULL
|
|
111
123
|
`;
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
|
|
126
|
+
markFailed(eventId: string, error: string): number {
|
|
127
|
+
this.sql`
|
|
128
|
+
UPDATE pi_turn_event_outbox
|
|
129
|
+
SET attempt_count = attempt_count + 1, last_error = ${error}
|
|
130
|
+
WHERE event_id = ${eventId}
|
|
131
|
+
AND delivered_at IS NULL AND quarantined_at IS NULL
|
|
132
|
+
`;
|
|
133
|
+
return this.find(eventId)?.attemptCount ?? 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
quarantine(eventId: string, quarantinedAt: number, error: string): void {
|
|
137
|
+
this.sql`
|
|
138
|
+
UPDATE pi_turn_event_outbox
|
|
139
|
+
SET quarantined_at = ${quarantinedAt}, last_error = ${error}
|
|
140
|
+
WHERE event_id = ${eventId} AND delivered_at IS NULL
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
find(eventId: string): StoredRuntimeEvent | null {
|
|
115
145
|
const row = this.sql<RuntimeEventRow>`
|
|
116
|
-
SELECT event_id, body, created_at, delivered_at
|
|
146
|
+
SELECT event_id, body, created_at, delivered_at,
|
|
147
|
+
attempt_count, quarantined_at, last_error
|
|
117
148
|
FROM pi_turn_event_outbox
|
|
118
149
|
WHERE event_id = ${eventId}
|
|
119
150
|
`[0];
|
package/src/db/schema.ts
CHANGED
|
@@ -102,8 +102,25 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
102
102
|
event_id TEXT PRIMARY KEY,
|
|
103
103
|
body TEXT NOT NULL,
|
|
104
104
|
created_at INTEGER NOT NULL,
|
|
105
|
-
delivered_at INTEGER
|
|
105
|
+
delivered_at INTEGER,
|
|
106
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
107
|
+
quarantined_at INTEGER,
|
|
108
|
+
last_error TEXT
|
|
106
109
|
)`;
|
|
110
|
+
const runtimeEventColumns = new Set(
|
|
111
|
+
sql<{ name: string }>`PRAGMA table_info(pi_turn_event_outbox)`
|
|
112
|
+
.map((column) => column.name),
|
|
113
|
+
);
|
|
114
|
+
if (!runtimeEventColumns.has("attempt_count")) {
|
|
115
|
+
sql`ALTER TABLE pi_turn_event_outbox
|
|
116
|
+
ADD COLUMN attempt_count INTEGER NOT NULL DEFAULT 0`;
|
|
117
|
+
}
|
|
118
|
+
if (!runtimeEventColumns.has("quarantined_at")) {
|
|
119
|
+
sql`ALTER TABLE pi_turn_event_outbox ADD COLUMN quarantined_at INTEGER`;
|
|
120
|
+
}
|
|
121
|
+
if (!runtimeEventColumns.has("last_error")) {
|
|
122
|
+
sql`ALTER TABLE pi_turn_event_outbox ADD COLUMN last_error TEXT`;
|
|
123
|
+
}
|
|
107
124
|
sql`CREATE TABLE IF NOT EXISTS pi_telemetry_outbox (
|
|
108
125
|
event_id TEXT PRIMARY KEY,
|
|
109
126
|
body TEXT NOT NULL,
|
package/src/runtime.ts
CHANGED
|
@@ -129,6 +129,7 @@ import {
|
|
|
129
129
|
|
|
130
130
|
const SCHEDULED_STABLE_TIMEOUT_MS = 30_000;
|
|
131
131
|
const TURN_EVENT_RETRY_SECONDS = 10;
|
|
132
|
+
const RUNTIME_EVENT_MAX_ATTEMPTS = 5;
|
|
132
133
|
|
|
133
134
|
// 计划续跑丢失多久后由看门狗接手。必须显著长于一个正常执行片的时长,
|
|
134
135
|
// 否则会在健康的长执行片上误判并派发出重复的一片。
|
|
@@ -1103,25 +1104,41 @@ export abstract class AgentRuntimeKernel<
|
|
|
1103
1104
|
if (!turnEvents.onSubagentUsage) continue;
|
|
1104
1105
|
const confirmation = await turnEvents.onSubagentUsage(payload.event);
|
|
1105
1106
|
if (confirmation?.confirmed !== true) {
|
|
1106
|
-
|
|
1107
|
-
break;
|
|
1107
|
+
throw new Error("Runtime event was not confirmed");
|
|
1108
1108
|
}
|
|
1109
1109
|
} else {
|
|
1110
1110
|
if (!turnEvents.onLifecycleFact) continue;
|
|
1111
1111
|
const confirmation = await turnEvents.onLifecycleFact(payload.event);
|
|
1112
1112
|
if (confirmation?.confirmed !== true) {
|
|
1113
|
-
|
|
1114
|
-
break;
|
|
1113
|
+
throw new Error("Runtime event was not confirmed");
|
|
1115
1114
|
}
|
|
1116
1115
|
}
|
|
1117
1116
|
this.db.transaction(() =>
|
|
1118
1117
|
this.db.runtimeEvents.markDelivered(row.eventId, Date.now())
|
|
1119
1118
|
);
|
|
1120
1119
|
} catch (error) {
|
|
1120
|
+
const failure = errorText(error);
|
|
1121
|
+
const attemptCount = this.db.transaction(() => {
|
|
1122
|
+
const nextAttemptCount = this.db.runtimeEvents.markFailed(
|
|
1123
|
+
row.eventId,
|
|
1124
|
+
failure,
|
|
1125
|
+
);
|
|
1126
|
+
if (nextAttemptCount >= RUNTIME_EVENT_MAX_ATTEMPTS) {
|
|
1127
|
+
this.db.runtimeEvents.quarantine(row.eventId, Date.now(), failure);
|
|
1128
|
+
}
|
|
1129
|
+
return nextAttemptCount;
|
|
1130
|
+
});
|
|
1131
|
+
if (attemptCount >= RUNTIME_EVENT_MAX_ATTEMPTS) {
|
|
1132
|
+
console.warn(
|
|
1133
|
+
"[runtime-turn-event:quarantined]",
|
|
1134
|
+
json({ eventId: row.eventId, attemptCount, error: failure }),
|
|
1135
|
+
);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1121
1138
|
failed = true;
|
|
1122
1139
|
console.warn(
|
|
1123
1140
|
"[runtime-turn-event:degraded]",
|
|
1124
|
-
json({ eventId: row.eventId, error:
|
|
1141
|
+
json({ eventId: row.eventId, attemptCount, error: failure }),
|
|
1125
1142
|
);
|
|
1126
1143
|
break;
|
|
1127
1144
|
}
|