@figs-so/cli 1.2.0 → 1.3.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/SPEC.md +14 -3
- package/figs.mjs +26 -3
- package/package.json +1 -1
package/SPEC.md
CHANGED
|
@@ -119,9 +119,17 @@ crash mid-job leaves a visible, recoverable stub. A **report** files the outcome
|
|
|
119
119
|
report with no prior checkpoint is a job **born settled** (the single-sitting case). Nothing *external*
|
|
120
120
|
ever closes a run — only the agent's own report settles its job.
|
|
121
121
|
|
|
122
|
+
**Folding is local; the lifecycle still publishes.** The folded record is the job's *current* state; its
|
|
123
|
+
history is the sequence of lines. On push the CLI sends **both** — the folded record (the row) *and* the
|
|
124
|
+
raw fold lines (`runEvents`, §8) — so a reader rebuilds the timeline (opened → checkpoints → settled)
|
|
125
|
+
even when a whole account-free history is published in a single first push (which would otherwise collapse
|
|
126
|
+
to one "settled" snapshot). Each line's `eventId` is the dedupe key: one timeline entry per fold,
|
|
127
|
+
idempotent across re-pushes.
|
|
128
|
+
|
|
122
129
|
| Field | Type | Req | Meaning |
|
|
123
130
|
|---|---|:--:|---|
|
|
124
131
|
| `id` | string | ✓ | Stable id (upsert key). |
|
|
132
|
+
| `eventId` | string | | Machine-minted per-fold id (`evt-…`) — **plumbing, never typed** (like the agent UUID, §4). Survives folding: a job's id collapses many lines into one record, but each line keeps its own. Lets a reader rebuild the per-fold timeline from the raw lines published as `runEvents` (§8). Absent on hand-authored/legacy lines — they fold to one published snapshot, as before. |
|
|
125
133
|
| `ts` | string (ISO-8601 w/ offset) | ✓ | When it ran. Machine-stamped by the CLI, never typed. |
|
|
126
134
|
| `unit` | string | | The `Unit.id` this run is about. |
|
|
127
135
|
| `period` | string | | |
|
|
@@ -257,7 +265,8 @@ minimum CLI version requires Bearer.)
|
|
|
257
265
|
{
|
|
258
266
|
"workspaceId": "<uuid>", // from config.json
|
|
259
267
|
"agent": { /* agent.json */ },
|
|
260
|
-
"runs":
|
|
268
|
+
"runs": [ /* runs.jsonl folded by id */ ], // optional — the current state per job (the row)
|
|
269
|
+
"runEvents": [ /* runs.jsonl raw fold lines, eventId-bearing only */ ], // optional — rebuilds the timeline
|
|
261
270
|
"asks": [ /* asks.jsonl */ ], // optional
|
|
262
271
|
"messages": [ /* messages.jsonl */ ], // optional — transcribed replies the reader lacks
|
|
263
272
|
"confirmRename": true // optional — `figs push --rename`: confirm a real name change
|
|
@@ -265,8 +274,10 @@ minimum CLI version requires Bearer.)
|
|
|
265
274
|
```
|
|
266
275
|
2. **Each attached file** → `POST {endpoint}/api/artifacts/upload`, base64-encoded, hash-verified.
|
|
267
276
|
|
|
268
|
-
The server upserts the agent by `id` and runs/asks by `id`; it dedupes messages by event `id`; it
|
|
269
|
-
|
|
277
|
+
The server upserts the agent by `id` and runs/asks by `id`; it dedupes messages by event `id`; it
|
|
278
|
+
**rebuilds each run's timeline from `runEvents`, one entry per fold, deduped by `eventId`** (a run with no
|
|
279
|
+
`runEvents` — an older CLI, or legacy/hand-authored lines without an `eventId` — keeps one timeline entry
|
|
280
|
+
per content-changing push, the prior behavior); it never deletes. An agent **self-registers** on first push. **A push never walks a record backwards:** the
|
|
270
281
|
server refuses a fold older than the record's stored close/settle (a stale machine pushing old state)
|
|
271
282
|
and accepts a newer one (a legitimate reopen — the `warn` → `ok` evolution). **A push never re-homes an
|
|
272
283
|
agent:** a `workspaceId` differing from the agent's registered home is rejected `409`
|
package/figs.mjs
CHANGED
|
@@ -386,6 +386,19 @@ function genId(prefix) {
|
|
|
386
386
|
return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
// A run line's stable, machine-minted identity (the plumbing-id class, rule 7 —
|
|
390
|
+
// no verb accepts it; like the agent UUID). It survives folding: a job's id
|
|
391
|
+
// collapses many lines into one record, but each line keeps its own eventId.
|
|
392
|
+
// `figs push` sends the raw fold lines (with these ids) as `runEvents` so the
|
|
393
|
+
// app can rebuild the full in-flight timeline — opened → checkpoints → settled —
|
|
394
|
+
// even when a long account-free history is published in a single first link-late
|
|
395
|
+
// push (it would otherwise fold to one "settled" snapshot, losing the lifecycle).
|
|
396
|
+
// UUID-backed so it's a collision-free dedupe key: the server appends one
|
|
397
|
+
// timeline row per eventId, ON CONFLICT DO NOTHING, so re-pushes never duplicate.
|
|
398
|
+
function genEventId() {
|
|
399
|
+
return `evt-${randomUUID()}`
|
|
400
|
+
}
|
|
401
|
+
|
|
389
402
|
// ---------- local validation (the spec's common mistakes, caught on write) ----
|
|
390
403
|
// The server's schema stays the source of truth; these catch what hand-authors
|
|
391
404
|
// and flag typos get wrong, with errors that teach the fix.
|
|
@@ -1816,7 +1829,7 @@ async function reportCmd() {
|
|
|
1816
1829
|
const id = idGiven || genId("r")
|
|
1817
1830
|
// Before the append, so "new" means new-to-this-outbox (the typo catch).
|
|
1818
1831
|
const isNew = !foldById(readJsonl("runs.jsonl")).some((r) => r.id === id)
|
|
1819
|
-
const run = { id, ts: nowIso(), result, state: "settled" }
|
|
1832
|
+
const run = { id, eventId: genEventId(), ts: nowIso(), result, state: "settled" }
|
|
1820
1833
|
const unit = flag("--unit")
|
|
1821
1834
|
if (unit) run.unit = unit
|
|
1822
1835
|
const period = flag("--period")
|
|
@@ -1881,7 +1894,7 @@ async function checkpointCmd() {
|
|
|
1881
1894
|
}
|
|
1882
1895
|
// Before the append, so "new" means new-to-this-outbox (the teaching line).
|
|
1883
1896
|
const isNew = !foldById(readJsonl("runs.jsonl")).some((r) => r.id === id)
|
|
1884
|
-
const run = { id, ts: nowIso(), result: note, state: "in-flight" }
|
|
1897
|
+
const run = { id, eventId: genEventId(), ts: nowIso(), result: note, state: "in-flight" }
|
|
1885
1898
|
const unit = flag("--unit")
|
|
1886
1899
|
if (unit) run.unit = unit
|
|
1887
1900
|
const period = flag("--period")
|
|
@@ -2164,10 +2177,19 @@ async function doPush() {
|
|
|
2164
2177
|
const agentJson = readJson(join(repoDir, "agent.json"), null)
|
|
2165
2178
|
if (!agentJson) return fail("missing .figs/agent.json — author it, then `figs doctor`")
|
|
2166
2179
|
const agent = { ...agentJson, id: config.agentId }
|
|
2167
|
-
const
|
|
2180
|
+
const runLines = readJsonl("runs.jsonl")
|
|
2181
|
+
const runs = foldById(runLines)
|
|
2168
2182
|
const asks = foldById(readJsonl("asks.jsonl"))
|
|
2169
2183
|
// Messages are immutable events — sent whole (no fold); the server dedupes by id.
|
|
2170
2184
|
const messages = readJsonl("messages.jsonl")
|
|
2185
|
+
// The raw fold lines ride alongside the folded spine: `runs` (folded) still
|
|
2186
|
+
// drives the run row + validation; `runEvents` lets the server rebuild the
|
|
2187
|
+
// per-fold timeline (opened → checkpoints → settled), so a link-late first
|
|
2188
|
+
// push doesn't collapse a whole offline lifecycle into one "settled" snapshot.
|
|
2189
|
+
// Only eventId-bearing lines (CLI-written) — legacy/hand-authored lines lack
|
|
2190
|
+
// the dedupe key, so the server keeps its current one-row-per-push behavior
|
|
2191
|
+
// for them (and runs they belong to are untouched).
|
|
2192
|
+
const runEvents = runLines.filter((r) => r.eventId)
|
|
2171
2193
|
// One-time confirm that a name change on an already-registered id is a real
|
|
2172
2194
|
// rename, not a copied folder (the server's rename guard refuses it otherwise).
|
|
2173
2195
|
const confirmRename = hasFlag("--rename")
|
|
@@ -2192,6 +2214,7 @@ async function doPush() {
|
|
|
2192
2214
|
workspaceId: config.workspaceId,
|
|
2193
2215
|
agent,
|
|
2194
2216
|
runs,
|
|
2217
|
+
runEvents,
|
|
2195
2218
|
asks,
|
|
2196
2219
|
messages,
|
|
2197
2220
|
...(confirmRename ? { confirmRename: true } : {}),
|