@agentprojectcontext/apx 1.74.1 → 1.74.2
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/core/stores/routines.js +52 -0
- package/src/interfaces/web/dist/assets/{index-DUXlrW8P.js → index-CQTIGYCu.js} +180 -180
- package/src/interfaces/web/dist/assets/index-CQTIGYCu.js.map +1 -0
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/package-lock.json +15 -15
- package/src/interfaces/web/dist/assets/index-DUXlrW8P.js.map +0 -1
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { CronExpressionParser } from "cron-parser";
|
|
6
6
|
import { nowIso, isoToMs } from "../util/time.js";
|
|
7
|
+
import { shortId } from "../util/ids.js";
|
|
7
8
|
|
|
8
9
|
function routinesPath(storagePath) {
|
|
9
10
|
// storagePath is always ~/.apx/projects/{apxId}/ — flat, no .apc subdir needed.
|
|
@@ -82,13 +83,58 @@ export function computeNextRun(routine, baseMs = Date.now()) {
|
|
|
82
83
|
return null;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// --------------------- ids + migration --------------------------------------
|
|
87
|
+
|
|
88
|
+
// Routines are addressed by `name` everywhere (getRoutine/deleteRoutine/
|
|
89
|
+
// setEnabled/updateRunState), but per-routine memory is keyed by `id`
|
|
90
|
+
// (stores/routine-memory.js). Records written before ids existed have none, so
|
|
91
|
+
// every one of them resolved to the shared `routines/_unknown/memory.md`.
|
|
92
|
+
//
|
|
93
|
+
// This migration is deliberately NOT inside readFile(): that helper is on the
|
|
94
|
+
// scheduler's 5s polling path, and writing from it would mean write
|
|
95
|
+
// amplification plus a read-modify-write race against a concurrent CLI edit.
|
|
96
|
+
// Instead the public read entry points call this, and it early-returns without
|
|
97
|
+
// touching disk once every record has an id — so the write happens once per
|
|
98
|
+
// project, ever.
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Backfill `id` on any routine record that predates the field.
|
|
102
|
+
* Returns the number of records migrated (0 when there was nothing to do).
|
|
103
|
+
*/
|
|
104
|
+
export function ensureRoutineIds(storagePath) {
|
|
105
|
+
const routines = readFile(storagePath);
|
|
106
|
+
const missing = routines.filter((r) => r && !r.id);
|
|
107
|
+
if (missing.length === 0) return 0;
|
|
108
|
+
|
|
109
|
+
for (const r of missing) r.id = shortId("r");
|
|
110
|
+
writeFile(storagePath, routines);
|
|
111
|
+
|
|
112
|
+
// Anything already written under routines/_unknown/ belonged to an
|
|
113
|
+
// indeterminate set of routines — we can't know which, so we leave it where
|
|
114
|
+
// it is rather than guess, and say so out loud once.
|
|
115
|
+
const orphan = path.join(storagePath, "routines", "_unknown");
|
|
116
|
+
if (fs.existsSync(orphan)) {
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.warn(
|
|
119
|
+
`[apx] routines: assigned ids to ${missing.length} routine(s). Pre-existing shared\n` +
|
|
120
|
+
` memory at ${orphan} was left untouched — it cannot be attributed to a single\n` +
|
|
121
|
+
` routine. Copy anything worth keeping into the per-routine memory files.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return missing.length;
|
|
125
|
+
}
|
|
126
|
+
|
|
85
127
|
// --------------------- CRUD -------------------------------------------------
|
|
86
128
|
|
|
87
129
|
export function listRoutines(projectPath) {
|
|
130
|
+
ensureRoutineIds(projectPath);
|
|
88
131
|
return readFile(projectPath);
|
|
89
132
|
}
|
|
90
133
|
|
|
91
134
|
export function getRoutine(projectPath, name) {
|
|
135
|
+
// Callers hand the record straight to the runner, which needs `id` for
|
|
136
|
+
// per-routine memory.
|
|
137
|
+
ensureRoutineIds(projectPath);
|
|
92
138
|
return readFile(projectPath).find((r) => r.name === name) || null;
|
|
93
139
|
}
|
|
94
140
|
|
|
@@ -100,6 +146,10 @@ export function upsertRoutine(storagePath, { name, kind, schedule, spec, enabled
|
|
|
100
146
|
const prev = idx >= 0 ? routines[idx] : null;
|
|
101
147
|
const next = computeNextRun({ schedule, last_run_at: null });
|
|
102
148
|
const entry = {
|
|
149
|
+
// `entry` is rebuilt from scratch on every upsert, so the id MUST be
|
|
150
|
+
// carried over explicitly (same as created_at below). Dropping it here
|
|
151
|
+
// would re-id the routine on every edit and orphan its memory directory.
|
|
152
|
+
id: prev?.id || shortId("r"),
|
|
103
153
|
name,
|
|
104
154
|
kind,
|
|
105
155
|
schedule,
|
|
@@ -167,6 +217,8 @@ export function updateRunState(projectPath, name, { last_run_at, last_status, la
|
|
|
167
217
|
}
|
|
168
218
|
|
|
169
219
|
export function getDueRoutines(projectPath, nowStr) {
|
|
220
|
+
// The runner keys per-routine memory off `id`, so due records must carry one.
|
|
221
|
+
ensureRoutineIds(projectPath);
|
|
170
222
|
return readFile(projectPath).filter((r) => {
|
|
171
223
|
if (!r.enabled) return false;
|
|
172
224
|
// CRITICAL: If the schedule cannot be parsed, NEVER run it.
|