@dumbmice/pi-todo-loop 0.1.0 → 0.1.1
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/AGENTS.md +1 -0
- package/CHANGELOG.md +19 -0
- package/docs/behaviour.md +6 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/inject.ts +21 -1
package/AGENTS.md
CHANGED
|
@@ -147,6 +147,7 @@ introduces an edge.
|
|
|
147
147
|
| `deleted` tasks leave list, open set and signature | `deleted tasks are excluded from tasks, open and signature` |
|
|
148
148
|
| malformed entries degrade to the empty snapshot | `malformed or missing state degrades to the empty snapshot` |
|
|
149
149
|
| config defaults survive garbage; fields are coerced | `missing or malformed config falls back to defaults`, `valid fields override, invalid ones are ignored` |
|
|
150
|
+
| the reconciliation clause appears only after one continuation | `the reconciliation clause is off on the first nudge`, `the reconciliation clause appears after one continuation, update before remove`, `the block asks for reconciliation only from the second request of a chain` |
|
|
150
151
|
| model-authored task text is sanitised before it enters a request | `a hostile task subject cannot break out of the tag` |
|
|
151
152
|
| the injection shows open tasks only, bounded, `activeForm` for in_progress | `injection lists open tasks with their status`, `injection is bounded and reports the overflow`, `injection uses activeForm only for in_progress`, `injection tolerates an empty subject` |
|
|
152
153
|
| a status change moves the signature | `signature changes when a status changes` |
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.1] - 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The injected block escalates once: from the first continuation of a chain it asks the model to
|
|
12
|
+
reconcile a drifted list — update statuses, add what is missing, remove what no longer applies, in
|
|
13
|
+
that order — so a stale list has an exit short of burning the round cap. The first request of a
|
|
14
|
+
chain still only asks for status moves.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- The publish workflow survives a stale registry read: it asks for the version three times before
|
|
19
|
+
concluding it is absent, and treats a `previously published` refusal as success. It also reads
|
|
20
|
+
`npm pack --json` in both shapes (npm 10 returns an array, npm 11 an object keyed by package name),
|
|
21
|
+
which is what failed the first two publish runs before they could upload.
|
|
22
|
+
|
|
7
23
|
## [0.1.0] - 2026-11-04
|
|
8
24
|
|
|
9
25
|
Initial release.
|
|
@@ -48,6 +64,9 @@ Initial release.
|
|
|
48
64
|
- The continuation marker's constant body states the instruction ("Continue with the open todo
|
|
49
65
|
tasks…") instead of a self-closing tag, so `injectState: false` no longer continues with no
|
|
50
66
|
instruction at all, and a marker left in history is self-explanatory.
|
|
67
|
+
- The injected block escalates once: from the first continuation onwards it asks the model to
|
|
68
|
+
reconcile a drifted list (update statuses, add what is missing, remove what no longer applies —
|
|
69
|
+
in that order), which gives a stale list a deterministic exit short of burning the round cap.
|
|
51
70
|
- A list that goes open → closed in a turn whose only tool results were `todo` is announced once: the
|
|
52
71
|
loop's success state and its failure state are the same observable.
|
|
53
72
|
- A stale-list burn now says so: the round-cap reason appends `, list unchanged for N settles`.
|
package/docs/behaviour.md
CHANGED
|
@@ -52,6 +52,12 @@ compaction cannot remove it.
|
|
|
52
52
|
- **The ids are the todo tool's own**, except where the stored list supplies an unusable or duplicate
|
|
53
53
|
id: that row moves to a free number so each row can be addressed, which means a `todo` call using the
|
|
54
54
|
shown number can fail when the tool wrote the list that way.
|
|
55
|
+
- **The block escalates once.** On the first request of a chain it only asks for status
|
|
56
|
+
moves; from the first continuation onwards it adds a reconciliation clause — *"if this
|
|
57
|
+
list has drifted from the work, reconcile it now: update statuses, add what is missing,
|
|
58
|
+
and remove what no longer applies"* — with removal last. A first sighting should not
|
|
59
|
+
invite reshaping the list; after a nudge that left the list untouched, a drifted list is
|
|
60
|
+
the likelier explanation. Verified: 115 tests, including both sides of the gate.
|
|
55
61
|
- **A list cleared by `todo` calls alone is announced.** If every task goes from open to closed in a
|
|
56
62
|
turn whose only tool results were `todo`, a warning says so once. The loop's success state and its
|
|
57
63
|
failure state are the same observable.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -125,7 +125,7 @@ export default function todoLoop(pi: any, options: TodoLoopOptions = {}): void {
|
|
|
125
125
|
// alternation-requiring providers reject. So markers stay in history; only a
|
|
126
126
|
// marker sitting at the tail (the continuation request about to be made) is
|
|
127
127
|
// replaced by the state block, so the request never ends on user → user.
|
|
128
|
-
const text = { type: "text", text: renderInjection(snapshot, config) };
|
|
128
|
+
const text = { type: "text", text: renderInjection(snapshot, config, { reconcile: state.rounds >= 1 }) };
|
|
129
129
|
const record = (message: unknown): Record<string, unknown> | null =>
|
|
130
130
|
typeof message === "object" && message !== null ? (message as Record<string, unknown>) : null;
|
|
131
131
|
const isMarker = (message: unknown): boolean => record(message)?.customType === MARKER_TYPE;
|
package/src/inject.ts
CHANGED
|
@@ -52,7 +52,26 @@ export function sanitizeLabel(text: string): string {
|
|
|
52
52
|
return points.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").trim();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export
|
|
55
|
+
export interface InjectionOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Add the reconciliation clause. Off for the first nudge of a chain, on afterwards:
|
|
58
|
+
* handing a model an explicit "fix the list" instruction on its first sighting
|
|
59
|
+
* invites it to reshape the list instead of doing the work, while after one nudge a
|
|
60
|
+
* drifted list is the likelier explanation.
|
|
61
|
+
*/
|
|
62
|
+
reconcile?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const RECONCILE_CLAUSE = [
|
|
66
|
+
"If this list has drifted from the work, reconcile it now: update statuses,",
|
|
67
|
+
"add what is missing, and remove what no longer applies.",
|
|
68
|
+
].join("\n");
|
|
69
|
+
|
|
70
|
+
export function renderInjection(
|
|
71
|
+
snapshot: Snapshot,
|
|
72
|
+
config: LoopConfig,
|
|
73
|
+
options: InjectionOptions = {},
|
|
74
|
+
): string {
|
|
56
75
|
const shown = snapshot.open.slice(0, config.maxInjectedTasks);
|
|
57
76
|
const rows = shown.map(
|
|
58
77
|
(task) =>
|
|
@@ -72,6 +91,7 @@ export function renderInjection(snapshot: Snapshot, config: LoopConfig): string
|
|
|
72
91
|
"with the open tasks, and move a task to in_progress when you start it and to",
|
|
73
92
|
"completed only once it is actually verified — if a task cannot be verified now,",
|
|
74
93
|
"leave it open and say what is blocking it.",
|
|
94
|
+
...(options.reconcile ? [RECONCILE_CLAUSE] : []),
|
|
75
95
|
`</${INJECT_TAG}>`,
|
|
76
96
|
].join("\n");
|
|
77
97
|
}
|