shadok-ai 0.14.7 → 0.14.9

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.
@@ -18,19 +18,27 @@ Then let it steer you:
18
18
  ("de mémoire, à faire — dis-moi si c'est déjà réglé"), don't assert;
19
19
  - rows printed under **"related rows — they may NOT answer your question"** are
20
20
  leads, not an answer: you are still in the UNKNOWN case above;
21
- - a **stale** record (days old) → confirm before acting.
21
+ - a **stale** record (days old) → confirm before acting; the age shown is when
22
+ the claim was last CONFIRMED, not when the text was last edited;
23
+ - a row tagged **`· fact`** has no lifecycle — `current` or `stale`, never
24
+ "done": don't read its status as a position in open → resolved, and don't
25
+ close it because surrounding work finished. A **`· decision`** stands until
26
+ something supersedes it.
22
27
 
23
28
  **You are also pushed the changes.** Ahead of each turn you are given — a user
24
29
  message, or a scheduled/automated run — you may receive a
25
- `⟦ledger · N updates since your last message⟧` block listing what sibling agents
26
- resolved or decided since you last heard — each line `• [id] entity — status`.
27
- Treat it as current status (don't re-raise what it shows resolved). It is the
28
- push half; `check` is still there for older or specific items the block didn't
29
- carry.
30
+ `⟦ledger · N updates⟧` block listing what sibling agents resolved or decided
31
+ since you last heard — `• [id] entity — status ↳ what happened`, all on one
32
+ line. Treat it as current status
33
+ (don't re-raise what it shows resolved). It is the push half; `check` is still
34
+ there for older or specific items the block didn't carry.
30
35
 
31
36
  When you **resolve, decide, or change** something notable, record it so the next
32
37
  agent doesn't redo it:
33
- `node ~/.claude/skills/shadok-ledger/ledger.mjs record --entity "<name>" --status <resolved|open|in-progress|decided> --note "<line>" --source "<PR#/who>"`.
38
+ `node ~/.claude/skills/shadok-ledger/ledger.mjs record --entity "<name>" --status <resolved|open|in-progress> --note "<line>" --source "<PR#/who>"`.
39
+ Open the note with the sentence that says WHAT happened — it is what siblings get
40
+ pushed. For something with no lifecycle, pass `--kind fact` or `--kind decision`
41
+ instead of a status; to say you re-checked a row and nothing moved, `--verified`.
34
42
  To update an item you saw in the block, quote its handle instead of retyping the
35
43
  name: `… record --id <id> --status resolved --note "<line>"`.
36
44
 
@@ -37,7 +37,14 @@ you nothing.
37
37
  the search found no row that answers you, only rows sharing some words. You
38
38
  are still in the UNKNOWN case above. Read them as leads (they may point at the
39
39
  right topic under another name), never as a status to assert.
40
- - a **stale** row (many days old) → hedge and confirm before acting.
40
+ - a **stale** row (many days old) → hedge and confirm before acting. The age
41
+ shown is when the row's claim was last **confirmed**, not when its text was
42
+ last edited.
43
+ - a row tagged **`· fact`** has no lifecycle: it is `current` or `stale`, never
44
+ "done". Do not read its status as a position in open → resolved, and do not
45
+ close it because the work around it finished — it stays true.
46
+ - a row tagged **`· decision`** is a standing decision, not a phase of a task.
47
+ It holds until something supersedes it.
41
48
 
42
49
  This fires on status-dependent claims (a bug being open, a PR unmerged, a task
43
50
  undone, a campaign live). A **durable lesson/constraint** ("don't buy the
@@ -49,13 +56,46 @@ Record it so the next agent doesn't redo it:
49
56
 
50
57
  ```
51
58
  node ~/.claude/skills/shadok-ledger/ledger.mjs record \
52
- --entity "<name>" --status <resolved|open|in-progress|decided> \
59
+ --entity "<name>" --status <resolved|open|in-progress> \
53
60
  --note "<one line>" --source "<PR#, session, who>"
54
61
  ```
55
62
 
56
63
  Re-recording the same entity **supersedes** its row (no duplicates). Log facts —
57
64
  resolutions, decisions, state changes, launched actions — never chatter.
58
65
 
66
+ **Open the note with the one sentence that says what happened.** Its first
67
+ sentence is what siblings are shown in the `⟦ledger⟧` block pushed ahead of their
68
+ messages — the entity name alone cannot tell them what changed about it. Put the
69
+ consequence first, details after.
70
+
71
+ ## Is it a task, a fact, or a decision?
72
+
73
+ A **task** has a lifecycle, so a status is the right thing to carry and closing
74
+ it is normal. That is the default and needs no flag. The other two do:
75
+
76
+ ```
77
+ … record --entity "nightly import window" --kind fact \
78
+ --note "Runs 03:00-03:40 Europe/Paris, after the extraction job."
79
+ … record --entity "ledger stays a state table" --kind decision \
80
+ --note "Supersede, not append, so size is bounded by live topics."
81
+ ```
82
+
83
+ `--kind` replaces `--status` for these two (they get `current` / `decided`), because
84
+ neither has phases to report. This matters: a **fact** marked `resolved` stops
85
+ being read by anything that filters on open work, while remaining perfectly true
86
+ — which is how a job finished on the 9th was still believed pending on the 12th.
87
+
88
+ When you re-check a row and nothing has changed, say so instead of rewriting it:
89
+
90
+ ```
91
+ … record --id <id> --verified
92
+ ```
93
+
94
+ That refreshes its freshness only. It deliberately does **not** enter anyone's
95
+ pushed block — "I looked, nothing moved" is not news. Conversely, editing a note
96
+ does **not** refresh freshness: only a status (or `--verified`) counts as having
97
+ looked.
98
+
59
99
  Every row has a short **id** (shown as `[id]` in `check`/`list` and in the ledger
60
100
  block pushed ahead of your messages). To update a row you already saw, quote its
61
101
  id instead of retyping the name — no risk of a typo forking a second row:
@@ -0,0 +1,34 @@
1
+ // Types for the skill's pure core, so the SERVER can import the one source of
2
+ // the ledger's logic instead of growing a second copy of it (CLAUDE.md: this
3
+ // file is the single source, no duplication to drift). The core itself stays
4
+ // plain .mjs — it is copied verbatim into ~/.claude/skills/ at every boot and
5
+ // must run with no build step of its own.
6
+ export interface LedgerCoreRow {
7
+ id?: string;
8
+ entity: string;
9
+ status: string;
10
+ kind?: string;
11
+ note?: string;
12
+ source?: string;
13
+ updatedAt?: number;
14
+ verifiedAt?: number;
15
+ }
16
+ export type LedgerKind = "task" | "fact" | "decision";
17
+ export const KINDS: LedgerKind[];
18
+ export function normEntity(s: unknown): string;
19
+ export function normKind(k: unknown): LedgerKind | null;
20
+ export function rowKind(entry: unknown): LedgerKind;
21
+ export function resolveId<T extends LedgerCoreRow>(entries: T[], idOrPrefix: unknown): T | null;
22
+ export function upsertEntry<T extends LedgerCoreRow>(
23
+ entries: T[],
24
+ patch: Partial<LedgerCoreRow> & { verified?: boolean },
25
+ now: number,
26
+ newId?: string,
27
+ ): T[];
28
+ export function tokenize(s: unknown): string[];
29
+ export function searchEntries<T extends LedgerCoreRow>(
30
+ entries: T[],
31
+ query: string,
32
+ opts?: { strong?: number; weak?: number },
33
+ ): { strong: T[]; weak: T[] };
34
+ export function ageDays(entry: { updatedAt?: number; verifiedAt?: number }, now: number): number;
@@ -26,6 +26,52 @@ export function resolveId(entries, idOrPrefix) {
26
26
  return pre.length === 1 ? pre[0] : null;
27
27
  }
28
28
 
29
+ /* --------------------------------------------------------------------- *
30
+ * A row's KIND: a task, a fact, or a decision.
31
+ *
32
+ * The table used to treat all three identically, and they are not the same
33
+ * object. A TASK has a lifecycle, so a status is the right thing to carry and
34
+ * closing it is normal. A FACT has no lifecycle — only a date on which someone
35
+ * last confirmed it — so closing it makes it DISAPPEAR WHILE IT STAYS TRUE,
36
+ * which is how a finished job was still believed pending three days later. And
37
+ * `decided` was never a phase of the open → in-progress → resolved lifecycle it
38
+ * sat next to: it is a kind wearing a status's clothes.
39
+ *
40
+ * Design: docs/superpowers/specs/2026-10-13-ledger-kind-and-headline.md
41
+ * --------------------------------------------------------------------- */
42
+
43
+ export const KINDS = ["task", "fact", "decision"];
44
+
45
+ /** The status a kind with NO lifecycle gets when none is given, so an agent is
46
+ * never pushed into inventing a word for a thing that has no phases. Keeping
47
+ * `status` always present is what leaves every existing reader — the pushed
48
+ * delta, the GUI viewer, a consumer's recap filter — working untouched. */
49
+ const KIND_STATUS = { fact: "current", decision: "decided" };
50
+
51
+ /**
52
+ * Validate a `--kind`: one of KINDS, or null when absent. An unknown one THROWS
53
+ * rather than being stored — a typo that silently became a fourth kind would be
54
+ * invisible for exactly as long as nobody went looking.
55
+ */
56
+ export function normKind(k) {
57
+ const s = String(k ?? "").trim().toLowerCase();
58
+ if (!s) return null;
59
+ if (!KINDS.includes(s)) throw new Error(`unknown kind "${k}" — use one of: ${KINDS.join(", ")}`);
60
+ return s;
61
+ }
62
+
63
+ /**
64
+ * What kind a row IS — read, never written back. An absent `kind` reads as
65
+ * `decision` when the status is `decided` (that is what the word already meant)
66
+ * and as `task` otherwise, so the rows recorded before this existed keep their
67
+ * bytes and still render correctly. Assert only what you saw.
68
+ */
69
+ export function rowKind(entry) {
70
+ const k = String(entry?.kind ?? "").trim().toLowerCase();
71
+ if (KINDS.includes(k)) return k;
72
+ return normEntity(entry?.status) === "decided" ? "decision" : "task";
73
+ }
74
+
29
75
  /**
30
76
  * Upsert a row, two ways:
31
77
  * - `patch.id` present → UPDATE that exact row in place (resolved via resolveId),
@@ -35,20 +81,54 @@ export function resolveId(entries, idOrPrefix) {
35
81
  * its id (the handle is durable across every write), or create a new row with
36
82
  * `newId`.
37
83
  * `now` (epoch ms) and `newId` are injected so the core stays pure/testable.
84
+ *
85
+ * Two clocks, because an EDIT is not an OBSERVATION. `updatedAt` is when the row
86
+ * was last written; `verifiedAt` is when its claim was last confirmed, and it is
87
+ * what the staleness hedge reads. Rewriting a note used to rejuvenate a row
88
+ * nobody had re-checked in a month, which silently switched that hedge off:
89
+ *
90
+ * create ................................ both move
91
+ * --status given (even the same value) .. both move — re-asserting a status
92
+ * IS confirming it
93
+ * --note / --source / --entity only ..... updatedAt moves, verifiedAt does NOT
94
+ * --verified alone ...................... verifiedAt moves, updatedAt does NOT
95
+ *
96
+ * That last line is deliberate: a row whose `updatedAt` does not move stays out
97
+ * of every sibling's pushed delta, and "I re-checked, nothing changed" is not
98
+ * news — pushing it to every agent is the noise a state table exists to avoid.
99
+ * A row that predates `verifiedAt` inherits its old `updatedAt` as the moment it
100
+ * was last observed, which is the truth and makes the migration lazy, per row.
38
101
  */
39
102
  export function upsertEntry(entries, patch, now, newId) {
103
+ const kind = normKind(patch.kind); // refuses an unknown kind before anything is written
104
+ const statusGiven = patch.status != null && String(patch.status).trim() !== "";
105
+
40
106
  if (patch.id != null && String(patch.id).trim()) {
41
107
  const cur = resolveId(entries, patch.id);
42
108
  if (!cur) throw new Error(`no ledger entry with id "${patch.id}"`);
109
+ // `kind` is a property of the ENTITY, not of this write: keep the stored one
110
+ // when none is given, like the id.
111
+ const effKind = kind ?? normKind(cur.kind);
112
+ // Promoting a row to a lifecycle-less kind supplies that kind's status, so
113
+ // `--id X --kind fact` does not leave a stale `resolved` on a fact.
114
+ const status = statusGiven
115
+ ? String(patch.status).trim()
116
+ : kind && kind !== rowKind(cur) && KIND_STATUS[kind]
117
+ ? KIND_STATUS[kind]
118
+ : cur.status;
119
+ const edited =
120
+ statusGiven || kind != null || patch.note != null || patch.source != null || patch.entity != null;
121
+ const observed = statusGiven || patch.verified === true || status !== cur.status;
43
122
  const row = {
44
123
  id: cur.id,
45
124
  entity:
46
125
  patch.entity != null && String(patch.entity).trim() ? String(patch.entity).trim() : cur.entity,
47
- status:
48
- patch.status != null && String(patch.status).trim() ? String(patch.status).trim() : cur.status,
126
+ status,
127
+ ...(effKind ? { kind: effKind } : {}),
49
128
  ...((patch.note ?? cur.note) ? { note: String(patch.note ?? cur.note).trim() } : {}),
50
129
  ...((patch.source ?? cur.source) ? { source: String(patch.source ?? cur.source).trim() } : {}),
51
- updatedAt: now,
130
+ updatedAt: edited ? now : (cur.updatedAt ?? now),
131
+ verifiedAt: observed ? now : (cur.verifiedAt ?? cur.updatedAt ?? now),
52
132
  };
53
133
  return [...entries.filter((e) => e.id !== cur.id), row];
54
134
  }
@@ -56,13 +136,20 @@ export function upsertEntry(entries, patch, now, newId) {
56
136
  const key = normEntity(patch.entity);
57
137
  if (!key) throw new Error("entity is required");
58
138
  const existing = entries.find((e) => normEntity(e.entity) === key);
139
+ const effKind = kind ?? normKind(existing?.kind);
140
+ const status = statusGiven
141
+ ? String(patch.status).trim()
142
+ : (effKind && KIND_STATUS[effKind]) || String(patch.status ?? "").trim();
143
+ const observed = statusGiven || patch.verified === true || status !== existing?.status;
59
144
  const row = {
60
145
  id: existing?.id ?? newId,
61
146
  entity: String(patch.entity).trim(),
62
- status: String(patch.status ?? "").trim(),
147
+ status,
148
+ ...(effKind ? { kind: effKind } : {}),
63
149
  ...(patch.note ? { note: String(patch.note).trim() } : {}),
64
150
  ...(patch.source ? { source: String(patch.source).trim() } : {}),
65
151
  updatedAt: now,
152
+ verifiedAt: observed ? now : (existing?.verifiedAt ?? existing?.updatedAt ?? now),
66
153
  };
67
154
  return [...entries.filter((e) => normEntity(e.entity) !== key), row];
68
155
  }
@@ -181,7 +268,12 @@ export function searchEntries(entries, query, opts = {}) {
181
268
  };
182
269
  }
183
270
 
184
- /** Whole days since a row was last touched — the freshness the reflex hedges on. */
271
+ /**
272
+ * Whole days since the row's claim was last CONFIRMED — the freshness the reflex
273
+ * hedges on. `verifiedAt` first: reading `updatedAt` meant any edit, down to a
274
+ * reworded note, reset the age of a row nobody had re-checked. Falls back to
275
+ * `updatedAt` for the rows recorded before the field existed.
276
+ */
185
277
  export function ageDays(entry, now) {
186
- return Math.floor((now - (entry.updatedAt ?? now)) / 86_400_000);
278
+ return Math.floor((now - (entry.verifiedAt ?? entry.updatedAt ?? now)) / 86_400_000);
187
279
  }
@@ -10,7 +10,7 @@ import fs from "node:fs";
10
10
  import os from "node:os";
11
11
  import path from "node:path";
12
12
  import crypto from "node:crypto";
13
- import { upsertEntry, searchEntries, ageDays, resolveId, normEntity } from "./ledger-core.mjs";
13
+ import { upsertEntry, searchEntries, ageDays, resolveId, normEntity, rowKind, KINDS } from "./ledger-core.mjs";
14
14
 
15
15
  const FILE =
16
16
  (process.env.SHADOK_LEDGER_FILE || "").trim() ||
@@ -42,11 +42,17 @@ function mintId(rows) {
42
42
  return crypto.randomBytes(4).toString("hex");
43
43
  }
44
44
 
45
+ /** Flags that take NO value, listed rather than guessed: inferring "the next arg
46
+ * starts with -- so this one is a boolean" would misread `--note "--foo"`. */
47
+ const BOOL_FLAGS = new Set(["verified"]);
48
+
45
49
  /** `--key value` pairs → object. Values may be quoted by the shell already. */
46
50
  function parseFlags(args) {
47
51
  const out = {};
48
52
  for (let i = 0; i < args.length; i++) {
49
- if (args[i].startsWith("--")) out[args[i].slice(2)] = args[++i] ?? "";
53
+ if (!args[i].startsWith("--")) continue;
54
+ const key = args[i].slice(2);
55
+ out[key] = BOOL_FLAGS.has(key) ? true : (args[++i] ?? "");
50
56
  }
51
57
  return out;
52
58
  }
@@ -54,8 +60,17 @@ function parseFlags(args) {
54
60
  function fmt(e, now) {
55
61
  const age = ageDays(e, now);
56
62
  const when = age <= 0 ? "today" : age === 1 ? "1d ago" : `${age}d ago`;
63
+ const kind = rowKind(e);
64
+ // Say "verified" wherever the two clocks have parted company — and always for
65
+ // a fact, whose whole state IS its freshness. A bare age next to a row last
66
+ // confirmed a month ago but reworded yesterday is the lie this removes.
67
+ const parted = e.verifiedAt && e.updatedAt && e.verifiedAt !== e.updatedAt;
68
+ const stamp = kind === "fact" || parted ? `verified ${when}` : when;
69
+ // `task` is the default and the overwhelming majority: tagging it would be
70
+ // noise on every line. The two that change how a status must be READ are named.
71
+ const tag = kind === "task" ? "" : ` · ${kind}`;
57
72
  const handle = e.id ? `[${e.id}] ` : "";
58
- const head = `• ${handle}${e.entity} — ${e.status} (${when}${e.source ? ` · ${e.source}` : ""})`;
73
+ const head = `• ${handle}${e.entity} — ${e.status}${tag} (${stamp}${e.source ? ` · ${e.source}` : ""})`;
59
74
  return e.note ? `${head}\n ${e.note}` : head;
60
75
  }
61
76
 
@@ -89,11 +104,18 @@ if (cmd === "check") {
89
104
  // Two shapes: create/supersede by --entity (+ --status), or update an existing
90
105
  // row by its --id handle (change any of status/note/source; entity optional).
91
106
  const byId = f.id != null && String(f.id).trim();
92
- const idHasNoFields = f.status == null && f.note == null && f.source == null && f.entity == null;
93
- if (byId ? idHasNoFields : !f.entity || !f.status) {
107
+ const idHasNoFields =
108
+ f.status == null && f.note == null && f.source == null && f.entity == null &&
109
+ f.kind == null && f.verified == null;
110
+ // A fact or a decision has no lifecycle, so `--kind` stands in for `--status`:
111
+ // requiring one would only invite an invented word.
112
+ const hasStatus = !!f.status || !!f.kind;
113
+ if (byId ? idHasNoFields : !f.entity || !hasStatus) {
94
114
  console.error(
95
115
  'usage: ledger record --entity "<name>" --status <resolved|open|in-progress|decided> [--note "<line>"] [--source "<ref>"]\n' +
96
- ' or: ledger record --id <handle> [--status <…>] [--note "<line>"] [--source "<ref>"]',
116
+ ` or: ledger record --entity "<name>" --kind <${KINDS.join("|")}> [--note "<line>"] [--source "<ref>"]\n` +
117
+ ' or: ledger record --id <handle> [--status <…>] [--kind <…>] [--note "<line>"] [--source "<ref>"]\n' +
118
+ ' or: ledger record --id <handle> --verified (still true/current — nothing else changed)',
97
119
  );
98
120
  process.exit(2);
99
121
  }
@@ -102,7 +124,15 @@ if (cmd === "check") {
102
124
  try {
103
125
  rows = upsertEntry(
104
126
  rows0,
105
- { id: f.id, entity: f.entity, status: f.status, note: f.note, source: f.source },
127
+ {
128
+ id: f.id,
129
+ entity: f.entity,
130
+ status: f.status,
131
+ kind: f.kind,
132
+ verified: f.verified === true,
133
+ note: f.note,
134
+ source: f.source,
135
+ },
106
136
  now,
107
137
  mintId(rows0),
108
138
  );
@@ -112,7 +142,8 @@ if (cmd === "check") {
112
142
  }
113
143
  save(rows);
114
144
  const row = byId ? resolveId(rows, f.id) : rows.find((r) => normEntity(r.entity) === normEntity(f.entity));
115
- console.log(`recorded: ${row.entity} — ${row.status} [${row.id}]`);
145
+ const k = rowKind(row);
146
+ console.log(`recorded: ${row.entity} — ${row.status}${k === "task" ? "" : ` · ${k}`} [${row.id}]`);
116
147
  } else if (cmd === "list") {
117
148
  const rows = searchEntries(load(), "").strong;
118
149
  console.log(rows.length ? rows.map((e) => fmt(e, now)).join("\n") : "(ledger empty)");
package/dist/detect.d.ts CHANGED
@@ -136,5 +136,11 @@ export interface TypeOptions {
136
136
  settleMs?: number;
137
137
  /** How long to wait for the box to actually read empty after Ctrl-U. */
138
138
  clearMs?: number;
139
+ /**
140
+ * Injected context to put in the box BEFORE the message, as its own paste —
141
+ * not concatenated into it. See `typeIntoBox` for why that distinction is a
142
+ * correctness one.
143
+ */
144
+ prelude?: string;
139
145
  }
140
- export declare function typeIntoBox(pilot: TypingPilot, text: string, { attempts, appearMs, settleMs, clearMs }?: TypeOptions): Promise<boolean>;
146
+ export declare function typeIntoBox(pilot: TypingPilot, text: string, { attempts, appearMs, settleMs, clearMs, prelude }?: TypeOptions): Promise<boolean>;
package/dist/detect.js CHANGED
@@ -252,16 +252,42 @@ const MAX_CLEARS = 40;
252
252
  /** How long one press is given to show. Above the tmux mirror's fast poll
253
253
  * (`SCREEN_FAST_MS`), or a press that worked would read as one that did not. */
254
254
  const CLEAR_STEP_MS = 400;
255
- export async function typeIntoBox(pilot, text, { attempts = 6, appearMs = 2_000, settleMs = 400, clearMs = 1_000 } = {}) {
255
+ export async function typeIntoBox(pilot, text, { attempts = 6, appearMs = 2_000, settleMs = 400, clearMs = 1_000, prelude } = {}) {
256
256
  for (let attempt = 0; attempt < attempts; attempt++) {
257
257
  // Only "" and the resting state count as an empty box; anything else the
258
258
  // box shows once we paste is our paste (whole, or collapsed).
259
259
  const before = inputText(pilot.screen());
260
+ if (prelude) {
261
+ // TWO pastes, and the second must be the message. Claude Code keeps only
262
+ // the CURRENT paste inline and leaves every earlier one collapsed, so
263
+ // pasting separately is what puts the injected context inside the
264
+ // `<pasted_content>` wrapper and leaves the user's words outside it.
265
+ // Waiting for the first to land is load-bearing: fired back to back they
266
+ // can reach the pane as one paste event, which is exactly the single
267
+ // blob this exists to stop being.
268
+ pilot.paste(prelude);
269
+ try {
270
+ await pilot.waitFor((s) => inputText(s) !== before, { timeoutMs: appearMs });
271
+ }
272
+ catch {
273
+ // The prelude never showed. Fall through and paste the message anyway:
274
+ // losing the context is survivable, losing the message is not.
275
+ }
276
+ }
277
+ // The SCREEN, not `inputText`: the box's visible text is read off its `❯`
278
+ // line alone, so once a prelude has filled that line everything appended
279
+ // after it lands on continuation lines and `inputText` stops moving. Waiting
280
+ // on it then times out on a paste that arrived perfectly — which is exactly
281
+ // what the first version of this did, on every prelude long enough to wrap.
282
+ const afterPrelude = pilot.screen();
260
283
  pilot.paste(text);
261
284
  try {
262
285
  await pilot.waitFor((s) => {
263
286
  const now = inputText(s);
264
- return now !== "" && now !== before;
287
+ if (now === "" || now === before)
288
+ return false; // nothing in the box yet
289
+ // With no prelude this is the original condition, unchanged.
290
+ return !prelude || s !== afterPrelude;
265
291
  }, { timeoutMs: appearMs });
266
292
  return true;
267
293
  }
@@ -271,6 +297,8 @@ export async function typeIntoBox(pilot, text, { attempts = 6, appearMs = 2_000,
271
297
  const now = inputText(s);
272
298
  return now === "" || now === before;
273
299
  };
300
+ // `before` (not `beforeText`) on purpose: a retry re-pastes the prelude,
301
+ // so the box has to come back to what it was before EITHER paste.
274
302
  // Ctrl-U clears ONE line, not the box (seen on 2.1.296): a two-line paste
275
303
  // takes three presses — the last line, the newline, the first line. Two
276
304
  // presses left the first line behind and the next attempt pasted after it,
@@ -1 +1 @@
1
- {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
package/dist/ledger.d.ts CHANGED
@@ -18,9 +18,16 @@ export interface LedgerRow {
18
18
  id?: string;
19
19
  entity: string;
20
20
  status: string;
21
+ /** `task` | `fact` | `decision` — absent on every row recorded before it
22
+ * existed, which `rowKind` reads rather than rewriting them. */
23
+ kind?: string;
21
24
  note?: string;
22
25
  source?: string;
26
+ /** When the row was last WRITTEN. */
23
27
  updatedAt?: number;
28
+ /** When its claim was last CONFIRMED — the freshness, which an edit must not
29
+ * move. Absent on older rows; `ageDays` falls back to `updatedAt`. */
30
+ verifiedAt?: number;
24
31
  }
25
32
  /** The per-instance ledger file, keyed by the launch dir (same encoding as
26
33
  * channels/crons). Distinct launch dirs → distinct ledgers. */
@@ -46,17 +53,51 @@ export declare function deltaSince(rows: LedgerRow[], watermark: number, cap: nu
46
53
  rows: LedgerRow[];
47
54
  total: number;
48
55
  };
49
- /** The delta block the agent sees ahead of a human prompt. English (it is
50
- * written into the transcript — repo side). One line per row: `• [id] entity —
51
- * status (source)`; an overflow line when capped. */
56
+ /**
57
+ * The one line of the note that says WHAT happened, DERIVED rather than asked
58
+ * for. The block used to carry `[id] entity — status` and nothing else, so the
59
+ * entity *label* was the whole payload — and a label written as a topic name
60
+ * ("report regeneration, 15 test accounts") cannot say what changed about it.
61
+ * That is how work finished on the 9th was still believed pending on the 12th.
62
+ *
63
+ * Derived, because the alternative — a mandatory `--headline` — asks every agent
64
+ * for writing discipline (which nothing enforces) and covers none of the rows
65
+ * already recorded. The note's first sentence needs neither.
66
+ */
67
+ export declare function headline(note: string | undefined, max?: number): string;
68
+ /**
69
+ * The delta block the agent sees ahead of a prompt. English (it is written into
70
+ * the transcript — repo side). ONE line, within `BLOCK_MAX` (see above): per row
71
+ * `• [id] entity — status (source)`, the kind when it is not the default `task`,
72
+ * and `↳ <headline>` for what actually happened. Rows that do not fit the budget
73
+ * become the overflow pointer rather than a second line.
74
+ */
52
75
  export declare function formatLedgerBlock(rows: LedgerRow[], total: number): string;
53
- /** Prepend the block as its own leading lines. Idempotent. */
76
+ /** Prepend the block as its own leading line. Idempotent.
77
+ *
78
+ * NOT how the block reaches a live agent any more — it goes in as a separate
79
+ * PASTE (`pilot.submit(text, prelude)`), which is what keeps the user's message
80
+ * out of the `<pasted_content>` wrapper. Kept because it is the exact shape of
81
+ * every transcript written before that change, which `stripLedgerBlock` must go
82
+ * on understanding and which the tests build from. */
54
83
  export declare function markLedgerBlock(text: string, block: string): string;
55
- /** Does this text open with a pushed ledger block? */
84
+ /** Does this text open with a pushed ledger block?
85
+ *
86
+ * `⟧` CLOSES the header, it no longer ENDS the line: a one-line block continues
87
+ * with its rows after it. Testing `endsWith` here was the first thing the
88
+ * one-line format broke, and it fails open — detection returns false, nothing
89
+ * is stripped, and the whole block is shown to the user in the chat. The old
90
+ * multi-line shape satisfies this test too, so replayed transcripts are
91
+ * unaffected. */
56
92
  export declare function hasLedgerBlock(text: string): boolean;
57
- /** Remove a leading ledger block — its header line and the contiguous `• ` bullet
58
- * lines that follow it — leaving whatever came after (e.g. the ⟦platform⟧ header
59
- * and the user's message). A plain message is returned untouched. */
93
+ /** Remove a leading ledger block, leaving whatever came after (the ⟦platform⟧
94
+ * header and the user's message). A plain message is returned untouched.
95
+ *
96
+ * The block is one line now, so that is usually all this drops — but it must
97
+ * keep understanding the HISTORICAL multi-line shape (header, `• ` bullets,
98
+ * indented `↳` headlines), because `loadHistory` replays transcripts written
99
+ * before the format changed and a block it fails to strip is a block the user
100
+ * reads in the chat. */
60
101
  export declare function stripLedgerBlock(text: string): string;
61
102
  /** Where this instance stores its agents' ledger watermarks. Beside the table,
62
103
  * same launch-dir encoding, distinct name — writing one must never clobber the