@matthewfl/pi-contemplator 0.2.0 → 0.3.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/README.md
CHANGED
|
@@ -157,7 +157,7 @@ The observer and summarizer provide the durable memory substrate. The contemplat
|
|
|
157
157
|
|
|
158
158
|
## Installation
|
|
159
159
|
|
|
160
|
-
`pi-contemplator` 0.
|
|
160
|
+
`pi-contemplator` 0.3.0 requires **Pi 0.87.x**.
|
|
161
161
|
|
|
162
162
|
Install the published package through Pi:
|
|
163
163
|
|
|
@@ -203,4 +203,4 @@ npm run typecheck
|
|
|
203
203
|
|
|
204
204
|
## License
|
|
205
205
|
|
|
206
|
-
MIT. See [LICENSE](LICENSE).
|
|
206
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthewfl/pi-contemplator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A Pi extension that keeps long-running agentic sessions on track with background memory, contemplation, and structural review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"test:e2e": "node tests/e2e/rpc-contemplator.mjs && node tests/e2e/rpc-contemplator-history.mjs && node tests/e2e/rpc-summarizer.mjs && node tests/e2e/rpc-delivery.mjs && node tests/e2e/rpc-restore-review.mjs && node tests/e2e/rpc-compaction.mjs && node tests/e2e/rpc-compaction-resilience.mjs && node tests/e2e/rpc-memory-edges.mjs && node tests/e2e/rpc-observer-length.mjs && node tests/e2e/rpc-routing-isolation.mjs"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
48
|
-
"@earendil-works/pi-ai": "^0.
|
|
49
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
50
|
-
"@earendil-works/pi-tui": "^0.
|
|
47
|
+
"@earendil-works/pi-agent-core": "^0.87.0",
|
|
48
|
+
"@earendil-works/pi-ai": "^0.87.0",
|
|
49
|
+
"@earendil-works/pi-coding-agent": "^0.87.0",
|
|
50
|
+
"@earendil-works/pi-tui": "^0.87.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
54
|
-
"@earendil-works/pi-ai": "^0.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
56
|
-
"@earendil-works/pi-server": "^0.
|
|
57
|
-
"@earendil-works/pi-tui": "^0.
|
|
53
|
+
"@earendil-works/pi-agent-core": "^0.87.0",
|
|
54
|
+
"@earendil-works/pi-ai": "^0.87.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^0.87.0",
|
|
56
|
+
"@earendil-works/pi-server": "^0.87.0",
|
|
57
|
+
"@earendil-works/pi-tui": "^0.87.0",
|
|
58
58
|
"@types/node": "^22.0.0",
|
|
59
59
|
"typebox": "^1.1.38",
|
|
60
60
|
"typescript": "^5.6.0",
|
|
@@ -885,7 +885,10 @@ export class Contemplator {
|
|
|
885
885
|
// A clean final-action call is the end of the contemplator turn. Do not
|
|
886
886
|
// spend another model request asking it to narrate after its decision.
|
|
887
887
|
// Citation warnings leave the loop open so it can correct the action.
|
|
888
|
-
|
|
888
|
+
finishTurn: ({ message }) => {
|
|
889
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return undefined;
|
|
890
|
+
return intervention !== undefined && !finalActionWarned ? { action: "end" } : undefined;
|
|
891
|
+
},
|
|
889
892
|
...(supportsReasoning && selectedThinkingLevel !== "off" ? { reasoning: selectedThinkingLevel } : {}),
|
|
890
893
|
};
|
|
891
894
|
const runMessages: AgentMessage[] = [];
|
|
@@ -206,9 +206,12 @@ IMPORTANT: Now call record_observations to record the useful new observations fr
|
|
|
206
206
|
maxTokens: boundedMaxTokens(model, OBSERVER_AGENT_LOOP_MAX_TOKENS),
|
|
207
207
|
convertToLlm: replayTruncatedThinkingAsText,
|
|
208
208
|
toolExecution: "sequential",
|
|
209
|
-
|
|
209
|
+
finishTurn: ({ message }) => {
|
|
210
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return undefined;
|
|
210
211
|
turnCount++;
|
|
211
|
-
return doneCalled || (effectiveMaxTurns !== undefined && turnCount >= effectiveMaxTurns)
|
|
212
|
+
return doneCalled || (effectiveMaxTurns !== undefined && turnCount >= effectiveMaxTurns)
|
|
213
|
+
? { action: "end" }
|
|
214
|
+
: undefined;
|
|
212
215
|
},
|
|
213
216
|
...(reasoning && thinkingLevel !== "off" ? { reasoning: thinkingLevel } : {}),
|
|
214
217
|
};
|
|
@@ -165,7 +165,10 @@ export async function runStructuralReview(args: RunStructuralReviewArgs): Promis
|
|
|
165
165
|
maxTokens: boundedMaxTokens(args.model, remainingBudget()),
|
|
166
166
|
convertToLlm: (messages) => messages as Message[],
|
|
167
167
|
toolExecution: "sequential",
|
|
168
|
-
|
|
168
|
+
finishTurn: ({ message }) => {
|
|
169
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return undefined;
|
|
170
|
+
return terminal !== undefined || remainingBudget() === 0 ? { action: "end" } : undefined;
|
|
171
|
+
},
|
|
169
172
|
};
|
|
170
173
|
const promptMessage = prompt as AgentMessage;
|
|
171
174
|
// agentLoop receives the new prompt separately. Its context must therefore
|
|
@@ -197,8 +200,8 @@ export async function runStructuralReview(args: RunStructuralReviewArgs): Promis
|
|
|
197
200
|
// assistant usage that the real stream wrapper did not already account for.
|
|
198
201
|
const reportedOutputTokens = assistantOutputTokens(assistants);
|
|
199
202
|
totalOutputTokens += Math.max(0, reportedOutputTokens - streamedOutputTokens);
|
|
200
|
-
// A warned terminal call deliberately leaves
|
|
201
|
-
//
|
|
203
|
+
// A warned terminal call deliberately leaves finishTurn undecided so the
|
|
204
|
+
// reviewer can search/recall and replace it. If the reviewer instead
|
|
202
205
|
// ends its turn, honor the staged outcome exactly as the warning promised.
|
|
203
206
|
if (!terminal && warnedTerminal) {
|
|
204
207
|
terminal = warnedTerminal;
|
|
@@ -546,7 +546,12 @@ export async function runSummarizer(args: RunSummarizerArgs): Promise<Summarizer
|
|
|
546
546
|
if (calls.length > 1) return { block: true, reason: "Call done alone in a later response after sibling tool results are visible." };
|
|
547
547
|
return undefined;
|
|
548
548
|
},
|
|
549
|
-
|
|
549
|
+
finishTurn: ({ message }) => {
|
|
550
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return undefined;
|
|
551
|
+
return completedWithDone || (effectiveMaxTurns !== undefined && ++turnCount >= effectiveMaxTurns)
|
|
552
|
+
? { action: "end" }
|
|
553
|
+
: undefined;
|
|
554
|
+
},
|
|
550
555
|
...(requireToolCall ? { onPayload: (payload: unknown) => forceRequiredToolPayload(payload, args.model.api) } : {}),
|
|
551
556
|
...(reasoning && thinkingLevel !== "off" ? { reasoning: thinkingLevel } : {}),
|
|
552
557
|
};
|
|
@@ -7,9 +7,16 @@ import {
|
|
|
7
7
|
type MemoryCoverageCustomType,
|
|
8
8
|
} from "./types.js";
|
|
9
9
|
|
|
10
|
-
const SOURCE_ENTRY_TYPES = new Set(["
|
|
10
|
+
const SOURCE_ENTRY_TYPES = new Set(["custom_message", "branch_summary"]);
|
|
11
11
|
|
|
12
12
|
export function isSourceEntry(entry: Entry): boolean {
|
|
13
|
+
if (entry.type === "message") {
|
|
14
|
+
// Pi 0.87 persists its canonical system prompt as a message entry. It is
|
|
15
|
+
// extension/runtime configuration, not new conversation evidence; counting
|
|
16
|
+
// its large sections makes a fresh session immediately cross the observer
|
|
17
|
+
// threshold even though only a few hundred user-visible tokens exist.
|
|
18
|
+
return !isObject(entry.message) || entry.message.role !== "system";
|
|
19
|
+
}
|
|
13
20
|
return SOURCE_ENTRY_TYPES.has(entry.type);
|
|
14
21
|
}
|
|
15
22
|
|