maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909
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 +38 -19
- package/README.zh-CN.md +34 -19
- package/dist/mcp-capability-provider.js +4 -1
- package/dist/pi-transcript-tools.js +6 -0
- package/dist/pi-tui-layout.js +56 -10
- package/dist/pi-tui-mcp-status.js +2 -2
- package/dist/pi-tui-pickers.js +121 -30
- package/dist/pi-tui-runner.js +156 -42
- package/dist/pi-tui-todo.js +221 -0
- package/dist/pi-tui-transcript-viewer.js +134 -7
- package/dist/runtime-host-cli-context.js +70 -13
- package/dist/runtime-host-handoff-surface.js +51 -26
- package/dist/runtime-host-local-handoff.js +11 -1
- package/dist/runtime-host-service-manager.js +0 -3
- package/dist/runtime-host-session-channel.js +13 -0
- package/dist/runtime-host-session-driver.js +42 -1
- package/dist/tui-autocomplete-layout.js +3 -1
- package/dist/tui-copy-catalog.js +85 -6
- package/dist/tui-editor-render.js +36 -0
- package/dist/tui-mcp-control.js +41 -6
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/agent-run.js +1 -0
- package/node_modules/@maka/core/dist/attachments.js +3 -0
- package/node_modules/@maka/core/dist/canonical-json.js +27 -0
- package/node_modules/@maka/core/dist/capabilities.js +35 -0
- package/node_modules/@maka/core/dist/computer-use.js +0 -1
- package/node_modules/@maka/core/dist/display-redaction.js +18 -0
- package/node_modules/@maka/core/dist/llm-connections.js +13 -0
- package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
- package/node_modules/@maka/core/dist/model-metadata.js +4 -0
- package/node_modules/@maka/core/dist/redaction.js +35 -0
- package/node_modules/@maka/core/dist/run-composition.js +77 -2
- package/node_modules/@maka/core/dist/runtime-event.js +3 -0
- package/node_modules/@maka/core/dist/session.js +57 -6
- package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
- package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
- package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
- package/node_modules/@maka/core/package.json +3 -3
- package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
- package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
- package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
- package/node_modules/@maka/mcp/dist/index.js +337 -71
- package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
- package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
- package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
- package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
- package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
- package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
- package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
- package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
- package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
- package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
- package/node_modules/@maka/runtime/dist/model-history.js +0 -112
- package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
- package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
- package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
- package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
- package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
- package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
- package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
- package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
- package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
- package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
- package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
- package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
- package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
- package/node_modules/@maka/runtime/package.json +1 -0
- package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
- package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
- package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
- package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
- package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
- package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
- package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
- package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
- package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
- package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
- package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
- package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
- package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
- package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
- package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
- package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
- package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
- package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
- package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
- package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
- package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
- package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
- package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
- package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
- package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
- package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
- package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
- package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
- package/node_modules/@maka/storage/dist/credential-store.js +12 -63
- package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
- package/node_modules/@maka/storage/dist/git-exec.js +61 -0
- package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
- package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
- package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
- package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
- package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
- package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
- package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
- package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
- package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
- package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
- package/node_modules/@maka/storage/dist/session-store.js +14 -2
- package/node_modules/@maka/storage/dist/settings-store.js +8 -4
- package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
- package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
- package/node_modules/@maka/storage/dist/storage-id.js +28 -0
- package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
- package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
- package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
- package/package.json +1 -1
- package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
- package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
|
@@ -135,10 +135,10 @@ class SqliteScheduledTaskStore {
|
|
|
135
135
|
this.#lease.close();
|
|
136
136
|
}
|
|
137
137
|
async list() {
|
|
138
|
-
return
|
|
138
|
+
return this.readTasks().sort(compareScheduledTasksForList);
|
|
139
139
|
}
|
|
140
140
|
async get(id) {
|
|
141
|
-
return
|
|
141
|
+
return this.readTask(id);
|
|
142
142
|
}
|
|
143
143
|
async create(input, now = Date.now()) {
|
|
144
144
|
const normalized = normalizeCreateScheduledTaskInput(input, now);
|
|
@@ -163,171 +163,120 @@ class SqliteScheduledTaskStore {
|
|
|
163
163
|
runs: [],
|
|
164
164
|
lastError: null,
|
|
165
165
|
};
|
|
166
|
-
await this.
|
|
166
|
+
await this.enqueueWrite(() => {
|
|
167
|
+
this.#lease.database
|
|
168
|
+
.prepare(`
|
|
169
|
+
INSERT INTO workflow_scheduled_tasks(task_id, created_at, updated_at, record_json)
|
|
170
|
+
VALUES (?, ?, ?, ?)
|
|
171
|
+
`)
|
|
172
|
+
.run(task.id, task.createdAt, task.updatedAt, JSON.stringify(task));
|
|
173
|
+
});
|
|
167
174
|
return task;
|
|
168
175
|
}
|
|
169
176
|
async update(id, patch, now = Date.now()) {
|
|
170
177
|
const normalized = normalizeUpdateScheduledTaskInput(patch, now);
|
|
171
178
|
if (!normalized.ok)
|
|
172
179
|
throw storeError('invalid_input', normalized.message);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
...(Object.prototype.hasOwnProperty.call(normalized.value, 'maxFires')
|
|
211
|
-
? { maxFires }
|
|
212
|
-
: {}),
|
|
213
|
-
expiresAt,
|
|
214
|
-
nextFireAt,
|
|
215
|
-
updatedAt: now,
|
|
216
|
-
};
|
|
217
|
-
return updated;
|
|
218
|
-
}),
|
|
219
|
-
}));
|
|
220
|
-
if (!updated)
|
|
221
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
222
|
-
return updated;
|
|
180
|
+
return this.updateTask(id, (task) => {
|
|
181
|
+
if (task.status === 'completed' || task.status === 'expired') {
|
|
182
|
+
throw storeError('operation_conflict', 'Cannot update a terminal scheduled task');
|
|
183
|
+
}
|
|
184
|
+
const schedule = normalized.value.schedule ?? task.schedule;
|
|
185
|
+
const nextFireAt = task.status === 'active' ? computeRequiredNext(schedule, now) : null;
|
|
186
|
+
const effect = normalized.value.effect ?? task.effect;
|
|
187
|
+
const intentBody = normalized.value.intentBody ?? task.intent.body;
|
|
188
|
+
const expiresAt = Object.prototype.hasOwnProperty.call(normalized.value, 'expiresAt')
|
|
189
|
+
? (normalized.value.expiresAt ?? null)
|
|
190
|
+
: task.expiresAt;
|
|
191
|
+
const maxFires = Object.prototype.hasOwnProperty.call(normalized.value, 'maxFires')
|
|
192
|
+
? (normalized.value.maxFires ?? null)
|
|
193
|
+
: task.maxFires;
|
|
194
|
+
if (effect.kind !== 'notify' && !intentBody.trim()) {
|
|
195
|
+
throw storeError('invalid_input', 'Agent intent body is required');
|
|
196
|
+
}
|
|
197
|
+
if (maxFires !== null && maxFires <= task.fireCount) {
|
|
198
|
+
throw storeError('operation_conflict', 'maxFires must be greater than the current fireCount');
|
|
199
|
+
}
|
|
200
|
+
if (nextFireAt !== null && expiresAt !== null && nextFireAt >= expiresAt) {
|
|
201
|
+
throw storeError('invalid_input', 'Schedule must fire before expiresAt');
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
...task,
|
|
205
|
+
...(normalized.value.title !== undefined ? { title: normalized.value.title } : {}),
|
|
206
|
+
...(normalized.value.intentBody !== undefined
|
|
207
|
+
? { intent: { kind: 'text', body: normalized.value.intentBody } }
|
|
208
|
+
: {}),
|
|
209
|
+
schedule,
|
|
210
|
+
effect,
|
|
211
|
+
...(Object.prototype.hasOwnProperty.call(normalized.value, 'maxFires') ? { maxFires } : {}),
|
|
212
|
+
expiresAt,
|
|
213
|
+
nextFireAt,
|
|
214
|
+
updatedAt: now,
|
|
215
|
+
};
|
|
216
|
+
});
|
|
223
217
|
}
|
|
224
218
|
async pause(id, now = Date.now()) {
|
|
225
|
-
|
|
226
|
-
await this.mutate((state) => ({
|
|
227
|
-
...state,
|
|
228
|
-
tasks: state.tasks.map((task) => {
|
|
229
|
-
if (task.id !== id)
|
|
230
|
-
return task;
|
|
231
|
-
assertNoPendingClaim(state.claims, id);
|
|
232
|
-
updated = pauseScheduledTask(task, now);
|
|
233
|
-
return updated;
|
|
234
|
-
}),
|
|
235
|
-
}));
|
|
236
|
-
if (!updated)
|
|
237
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
238
|
-
return updated;
|
|
219
|
+
return this.updateTask(id, (task) => pauseScheduledTask(task, now));
|
|
239
220
|
}
|
|
240
221
|
async resume(id, now = Date.now()) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
result.expiresAt !== null &&
|
|
253
|
-
result.nextFireAt >= result.expiresAt) {
|
|
254
|
-
throw storeError('invalid_input', 'Schedule must fire before expiresAt');
|
|
255
|
-
}
|
|
256
|
-
updated = result;
|
|
257
|
-
return updated;
|
|
258
|
-
}),
|
|
259
|
-
}));
|
|
260
|
-
if (!updated)
|
|
261
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
262
|
-
return updated;
|
|
222
|
+
return this.updateTask(id, (task) => {
|
|
223
|
+
const result = resumeScheduledTask(task, now);
|
|
224
|
+
if ('error' in result)
|
|
225
|
+
throw storeError('operation_conflict', result.error);
|
|
226
|
+
if (result.nextFireAt !== null &&
|
|
227
|
+
result.expiresAt !== null &&
|
|
228
|
+
result.nextFireAt >= result.expiresAt) {
|
|
229
|
+
throw storeError('invalid_input', 'Schedule must fire before expiresAt');
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
});
|
|
263
233
|
}
|
|
264
234
|
async snooze(id, delayMs, now = Date.now()) {
|
|
265
235
|
if (!Number.isFinite(delayMs) || delayMs <= 0 || delayMs > 7 * 24 * 60 * 60 * 1000) {
|
|
266
236
|
throw storeError('invalid_input', 'Scheduled task snooze delay must be between 1 ms and 7 days');
|
|
267
237
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const nextFireAt = Math.max(now, task.nextFireAt) + Math.floor(delayMs);
|
|
279
|
-
if (task.expiresAt !== null && nextFireAt >= task.expiresAt) {
|
|
280
|
-
throw storeError('invalid_input', 'Snooze would move the task beyond expiresAt');
|
|
281
|
-
}
|
|
282
|
-
updated = { ...task, nextFireAt, updatedAt: now };
|
|
283
|
-
return updated;
|
|
284
|
-
}),
|
|
285
|
-
}));
|
|
286
|
-
if (!updated)
|
|
287
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
288
|
-
return updated;
|
|
238
|
+
return this.updateTask(id, (task) => {
|
|
239
|
+
if (task.status !== 'active' || task.nextFireAt === null) {
|
|
240
|
+
throw storeError('operation_conflict', 'Only active scheduled tasks can be snoozed');
|
|
241
|
+
}
|
|
242
|
+
const nextFireAt = Math.max(now, task.nextFireAt) + Math.floor(delayMs);
|
|
243
|
+
if (task.expiresAt !== null && nextFireAt >= task.expiresAt) {
|
|
244
|
+
throw storeError('invalid_input', 'Snooze would move the task beyond expiresAt');
|
|
245
|
+
}
|
|
246
|
+
return { ...task, nextFireAt, updatedAt: now };
|
|
247
|
+
});
|
|
289
248
|
}
|
|
290
249
|
async clearRunHistory(id, now = Date.now()) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return task;
|
|
297
|
-
assertNoPendingClaim(state.claims, id);
|
|
298
|
-
updated = { ...task, runs: [], lastError: null, updatedAt: now };
|
|
299
|
-
return updated;
|
|
300
|
-
}),
|
|
250
|
+
return this.updateTask(id, (task) => ({
|
|
251
|
+
...task,
|
|
252
|
+
runs: [],
|
|
253
|
+
lastError: null,
|
|
254
|
+
updatedAt: now,
|
|
301
255
|
}));
|
|
302
|
-
if (!updated)
|
|
303
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
304
|
-
return updated;
|
|
305
256
|
}
|
|
306
257
|
async remove(id) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
return true;
|
|
316
|
-
});
|
|
317
|
-
return { ...state, tasks: next };
|
|
258
|
+
await this.enqueueWrite(() => {
|
|
259
|
+
this.assertNoPendingClaim(id);
|
|
260
|
+
this.requireTask(id);
|
|
261
|
+
this.#lease.database
|
|
262
|
+
.prepare('DELETE FROM workflow_scheduled_tasks WHERE task_id = ?')
|
|
263
|
+
.run(id);
|
|
318
264
|
});
|
|
319
|
-
if (!found)
|
|
320
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
321
265
|
}
|
|
322
266
|
async claimNextDue(now = Date.now()) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
267
|
+
return this.enqueueWrite(() => {
|
|
268
|
+
// Due discovery still traverses the catalog to return every newly expired
|
|
269
|
+
// task. Only claim keys are needed, and only changed rows are written.
|
|
270
|
+
const claimRows = this.#lease.database
|
|
271
|
+
.prepare('SELECT task_id FROM workflow_scheduled_task_fires')
|
|
272
|
+
.all();
|
|
273
|
+
const claimedTaskIds = new Set(claimRows.map((row) => row.task_id));
|
|
274
|
+
const expired = [];
|
|
275
|
+
const tasks = this.readTasks().map((task) => {
|
|
328
276
|
if (task.status === 'active' && task.expiresAt !== null && now >= task.expiresAt) {
|
|
329
277
|
const next = { ...task, status: 'expired', nextFireAt: null, updatedAt: now };
|
|
330
278
|
expired.push(next);
|
|
279
|
+
this.writeTask(next);
|
|
331
280
|
return next;
|
|
332
281
|
}
|
|
333
282
|
return task;
|
|
@@ -335,186 +284,208 @@ class SqliteScheduledTaskStore {
|
|
|
335
284
|
const task = tasks
|
|
336
285
|
.filter((entry) => isScheduledTaskDue(entry, now) && !claimedTaskIds.has(entry.id))
|
|
337
286
|
.sort((left, right) => left.nextFireAt - right.nextFireAt || left.id.localeCompare(right.id))[0];
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return {
|
|
287
|
+
const claim = task ? createClaim(task, task.nextFireAt, now) : null;
|
|
288
|
+
if (claim)
|
|
289
|
+
this.insertClaim(claim);
|
|
290
|
+
return { claim, expired };
|
|
342
291
|
});
|
|
343
|
-
return { claim: claimed ?? null, expired };
|
|
344
292
|
}
|
|
345
293
|
async claimNow(id, now = Date.now()) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!task)
|
|
350
|
-
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
351
|
-
assertNoPendingClaim(state.claims, id);
|
|
294
|
+
return this.enqueueWrite(() => {
|
|
295
|
+
const task = this.requireTask(id);
|
|
296
|
+
this.assertNoPendingClaim(id);
|
|
352
297
|
if (task.status !== 'active') {
|
|
353
298
|
throw storeError('operation_conflict', 'Only active tasks can be triggered now');
|
|
354
299
|
}
|
|
355
300
|
if (task.expiresAt !== null && now >= task.expiresAt) {
|
|
356
301
|
throw storeError('operation_conflict', 'Scheduled task has expired');
|
|
357
302
|
}
|
|
358
|
-
|
|
359
|
-
|
|
303
|
+
const claim = createClaim(task, now, now);
|
|
304
|
+
this.insertClaim(claim);
|
|
305
|
+
return claim;
|
|
360
306
|
});
|
|
361
|
-
return claimed;
|
|
362
307
|
}
|
|
363
308
|
async listPendingFires() {
|
|
364
|
-
|
|
309
|
+
const rows = this.#lease.database
|
|
310
|
+
.prepare(`
|
|
311
|
+
SELECT record_json
|
|
312
|
+
FROM workflow_scheduled_task_fires
|
|
313
|
+
ORDER BY claimed_at, claim_id
|
|
314
|
+
`)
|
|
315
|
+
.all();
|
|
316
|
+
return rows.map((row, index) => decodeClaimRow(row, `row ${index + 1}`));
|
|
365
317
|
}
|
|
366
318
|
async bindFireExecution(claimId, execution) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (claim.task.effect.kind === 'notify') {
|
|
374
|
-
throw storeError('operation_conflict', `Scheduled task fire ${claimId} is not an Agent execution`);
|
|
375
|
-
}
|
|
376
|
-
if (claim.execution && !sameExecution(claim.execution, execution)) {
|
|
319
|
+
return this.updateClaim(claimId, (claim) => {
|
|
320
|
+
if (claim.task.effect.kind === 'notify') {
|
|
321
|
+
throw storeError('operation_conflict', `Scheduled task fire ${claimId} is not an Agent execution`);
|
|
322
|
+
}
|
|
323
|
+
if (claim.execution) {
|
|
324
|
+
if (!sameExecution(claim.execution, execution)) {
|
|
377
325
|
throw storeError('operation_conflict', `Scheduled task fire ${claimId} already has another execution`);
|
|
378
326
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
})
|
|
383
|
-
if (!updated) {
|
|
384
|
-
throw storeError('not_found', `No such scheduled task fire claim: ${claimId}`);
|
|
385
|
-
}
|
|
386
|
-
return structuredClone(updated);
|
|
327
|
+
return claim;
|
|
328
|
+
}
|
|
329
|
+
return { ...claim, execution: { ...execution } };
|
|
330
|
+
});
|
|
387
331
|
}
|
|
388
332
|
async setFireNativeState(claimId, nativeState) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (claim.nativeState === 'invoking' && nativeState !== 'invoking') {
|
|
399
|
-
throw storeError('operation_conflict', `Scheduled task fire ${claimId} already crossed delivery admission`);
|
|
400
|
-
}
|
|
401
|
-
updated = { ...claim, nativeState };
|
|
402
|
-
return updated;
|
|
403
|
-
}),
|
|
404
|
-
}));
|
|
405
|
-
if (!updated) {
|
|
406
|
-
throw storeError('not_found', `No such scheduled task fire claim: ${claimId}`);
|
|
407
|
-
}
|
|
408
|
-
return structuredClone(updated);
|
|
333
|
+
return this.updateClaim(claimId, (claim) => {
|
|
334
|
+
if (claim.task.effect.kind !== 'notify') {
|
|
335
|
+
throw storeError('operation_conflict', `Scheduled task fire ${claimId} is not a native effect`);
|
|
336
|
+
}
|
|
337
|
+
if (claim.nativeState === 'invoking' && nativeState !== 'invoking') {
|
|
338
|
+
throw storeError('operation_conflict', `Scheduled task fire ${claimId} already crossed delivery admission`);
|
|
339
|
+
}
|
|
340
|
+
return claim.nativeState === nativeState ? claim : { ...claim, nativeState };
|
|
341
|
+
});
|
|
409
342
|
}
|
|
410
343
|
async cancelWaitingNativeFire(taskId) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
344
|
+
return this.enqueueWrite(() => {
|
|
345
|
+
const row = this.#lease.database
|
|
346
|
+
.prepare('SELECT record_json FROM workflow_scheduled_task_fires WHERE task_id = ?')
|
|
347
|
+
.get(taskId);
|
|
348
|
+
if (!row)
|
|
349
|
+
return false;
|
|
350
|
+
const claim = decodeClaimRow(row, `task ${taskId}`);
|
|
416
351
|
if (claim.nativeState !== 'waiting_for_provider') {
|
|
417
352
|
throw storeError('operation_conflict', 'Scheduled task has a fire in progress');
|
|
418
353
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
354
|
+
this.#lease.database
|
|
355
|
+
.prepare('DELETE FROM workflow_scheduled_task_fires WHERE task_id = ?')
|
|
356
|
+
.run(taskId);
|
|
357
|
+
return true;
|
|
424
358
|
});
|
|
425
|
-
return cancelled;
|
|
426
359
|
}
|
|
427
360
|
async settleFire(claimId, run) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const claim = state.claims.find((entry) => entry.id === claimId);
|
|
361
|
+
return this.enqueueWrite(() => {
|
|
362
|
+
const claim = this.readClaim(claimId);
|
|
431
363
|
if (!claim)
|
|
432
364
|
throw new Error(`No such scheduled task fire claim: ${claimId}`);
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
365
|
+
const task = this.readTask(claim.taskId);
|
|
366
|
+
if (!task)
|
|
367
|
+
throw new Error(`No such scheduled task: claim ${claimId}`);
|
|
368
|
+
const record = {
|
|
369
|
+
id: run.id ?? randomUUID(),
|
|
370
|
+
at: run.at,
|
|
371
|
+
outcome: run.outcome,
|
|
372
|
+
message: [...run.message].slice(0, SCHEDULED_TASK_RUN_MESSAGE_MAX_CHARS).join(''),
|
|
373
|
+
...(run.sessionId ? { sessionId: run.sessionId } : {}),
|
|
374
|
+
...(run.runId ? { runId: run.runId } : {}),
|
|
375
|
+
};
|
|
376
|
+
const updated = nextScheduledTaskStateAfterFire(task, record);
|
|
377
|
+
this.writeTask(updated);
|
|
378
|
+
this.#lease.database
|
|
379
|
+
.prepare('DELETE FROM workflow_scheduled_task_fires WHERE claim_id = ?')
|
|
380
|
+
.run(claimId);
|
|
381
|
+
return updated;
|
|
448
382
|
});
|
|
449
|
-
if (!updated)
|
|
450
|
-
throw new Error(`No such scheduled task: claim ${claimId}`);
|
|
451
|
-
return updated;
|
|
452
|
-
}
|
|
453
|
-
async read() {
|
|
454
|
-
return (await this.readState()).tasks;
|
|
455
383
|
}
|
|
456
|
-
|
|
384
|
+
readTasks() {
|
|
457
385
|
const rows = this.#lease.database
|
|
458
386
|
.prepare(`
|
|
459
|
-
SELECT record_json
|
|
387
|
+
SELECT task_id, record_json
|
|
460
388
|
FROM workflow_scheduled_tasks
|
|
461
389
|
ORDER BY created_at, task_id
|
|
462
390
|
`)
|
|
463
391
|
.all();
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
392
|
+
return rows.map((row, index) => decodeTaskRow(row, `row ${index + 1}`));
|
|
393
|
+
}
|
|
394
|
+
readTask(id) {
|
|
395
|
+
const row = this.#lease.database
|
|
396
|
+
.prepare('SELECT task_id, record_json FROM workflow_scheduled_tasks WHERE task_id = ?')
|
|
397
|
+
.get(id);
|
|
398
|
+
if (!row)
|
|
399
|
+
return undefined;
|
|
400
|
+
return decodeTaskRow(row, `task ${id}`);
|
|
401
|
+
}
|
|
402
|
+
requireTask(id) {
|
|
403
|
+
const task = this.readTask(id);
|
|
404
|
+
if (!task)
|
|
405
|
+
throw storeError('not_found', `No such scheduled task: ${id}`);
|
|
406
|
+
return task;
|
|
407
|
+
}
|
|
408
|
+
readClaim(id) {
|
|
409
|
+
const row = this.#lease.database
|
|
410
|
+
.prepare('SELECT record_json FROM workflow_scheduled_task_fires WHERE claim_id = ?')
|
|
411
|
+
.get(id);
|
|
412
|
+
if (!row)
|
|
413
|
+
return undefined;
|
|
414
|
+
const claim = decodeClaimRow(row, `claim ${id}`);
|
|
415
|
+
if (claim.id !== id)
|
|
416
|
+
throw new Error(`Invalid scheduled task fire claim identity: ${id}`);
|
|
417
|
+
return claim;
|
|
418
|
+
}
|
|
419
|
+
assertNoPendingClaim(taskId) {
|
|
420
|
+
const pending = this.#lease.database
|
|
421
|
+
.prepare('SELECT 1 FROM workflow_scheduled_task_fires WHERE task_id = ?')
|
|
422
|
+
.get(taskId);
|
|
423
|
+
if (pending) {
|
|
424
|
+
throw storeError('operation_conflict', 'Scheduled task has a fire in progress');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
updateTask(id, update) {
|
|
428
|
+
return this.enqueueWrite(() => {
|
|
429
|
+
const task = this.requireTask(id);
|
|
430
|
+
this.assertNoPendingClaim(id);
|
|
431
|
+
const updated = update(task);
|
|
432
|
+
if (updated !== task)
|
|
433
|
+
this.writeTask(updated);
|
|
434
|
+
return updated;
|
|
469
435
|
});
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
436
|
+
}
|
|
437
|
+
updateClaim(id, update) {
|
|
438
|
+
return this.enqueueWrite(() => {
|
|
439
|
+
const claim = this.readClaim(id);
|
|
440
|
+
if (!claim)
|
|
441
|
+
throw storeError('not_found', `No such scheduled task fire claim: ${id}`);
|
|
442
|
+
const updated = update(claim);
|
|
443
|
+
if (updated !== claim) {
|
|
444
|
+
this.#lease.database
|
|
445
|
+
.prepare('UPDATE workflow_scheduled_task_fires SET record_json = ? WHERE claim_id = ?')
|
|
446
|
+
.run(JSON.stringify(updated), id);
|
|
480
447
|
}
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
...claim,
|
|
484
|
-
task: decodePersistedScheduledTask(markPersisted(claim.task)),
|
|
485
|
-
};
|
|
448
|
+
return structuredClone(updated);
|
|
486
449
|
});
|
|
487
|
-
return { tasks, claims };
|
|
488
450
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
this.
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
write(state) {
|
|
499
|
-
this.#lease.transaction('write', () => {
|
|
500
|
-
this.#lease.database.prepare('DELETE FROM workflow_scheduled_tasks').run();
|
|
501
|
-
this.#lease.database.prepare('DELETE FROM workflow_scheduled_task_fires').run();
|
|
502
|
-
const insert = this.#lease.database.prepare(`
|
|
503
|
-
INSERT INTO workflow_scheduled_tasks(task_id, created_at, updated_at, record_json)
|
|
504
|
-
VALUES (?, ?, ?, ?)
|
|
505
|
-
`);
|
|
506
|
-
for (const task of state.tasks) {
|
|
507
|
-
insert.run(task.id, task.createdAt, task.updatedAt, JSON.stringify(task));
|
|
508
|
-
}
|
|
509
|
-
const insertClaim = this.#lease.database.prepare(`
|
|
451
|
+
writeTask(task) {
|
|
452
|
+
this.#lease.database
|
|
453
|
+
.prepare('UPDATE workflow_scheduled_tasks SET updated_at = ?, record_json = ? WHERE task_id = ?')
|
|
454
|
+
.run(task.updatedAt, JSON.stringify(task), task.id);
|
|
455
|
+
}
|
|
456
|
+
insertClaim(claim) {
|
|
457
|
+
this.#lease.database
|
|
458
|
+
.prepare(`
|
|
510
459
|
INSERT INTO workflow_scheduled_task_fires(claim_id, task_id, claimed_at, record_json)
|
|
511
460
|
VALUES (?, ?, ?, ?)
|
|
512
|
-
`)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
461
|
+
`)
|
|
462
|
+
.run(claim.id, claim.taskId, claim.claimedAt, JSON.stringify(claim));
|
|
463
|
+
}
|
|
464
|
+
enqueueWrite(operation) {
|
|
465
|
+
const run = () => this.#lease.transaction('write', operation);
|
|
466
|
+
const next = this.queue.then(run, run);
|
|
467
|
+
this.queue = next.then(() => { }, () => { });
|
|
468
|
+
return next;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function decodeTaskRow(row, location) {
|
|
472
|
+
if (typeof row.record_json !== 'string') {
|
|
473
|
+
throw new Error(`Invalid scheduled task at ${location}`);
|
|
517
474
|
}
|
|
475
|
+
const task = decodePersistedScheduledTask(markPersisted(JSON.parse(row.record_json)));
|
|
476
|
+
if (task.id !== row.task_id)
|
|
477
|
+
throw new Error(`Invalid scheduled task identity: ${row.task_id}`);
|
|
478
|
+
return task;
|
|
479
|
+
}
|
|
480
|
+
function decodeClaimRow(row, location) {
|
|
481
|
+
if (typeof row.record_json !== 'string') {
|
|
482
|
+
throw new Error(`Invalid scheduled task fire claim at ${location}`);
|
|
483
|
+
}
|
|
484
|
+
const claim = JSON.parse(row.record_json);
|
|
485
|
+
return {
|
|
486
|
+
...claim,
|
|
487
|
+
task: decodePersistedScheduledTask(markPersisted(claim.task)),
|
|
488
|
+
};
|
|
518
489
|
}
|
|
519
490
|
function computeRequiredNext(schedule, now) {
|
|
520
491
|
const next = computeNextFireAt(schedule, now);
|
|
@@ -532,11 +503,6 @@ function createClaim(task, scheduledFor, claimedAt) {
|
|
|
532
503
|
task: structuredClone(task),
|
|
533
504
|
};
|
|
534
505
|
}
|
|
535
|
-
function assertNoPendingClaim(claims, taskId) {
|
|
536
|
-
if (claims.some((claim) => claim.taskId === taskId)) {
|
|
537
|
-
throw storeError('operation_conflict', 'Scheduled task has a fire in progress');
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
506
|
function storeError(code, message) {
|
|
541
507
|
return new ScheduledTaskStoreError(code, message);
|
|
542
508
|
}
|