pi-background-tasks 0.7.6 → 0.7.7
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/PUBLISHING.md +7 -7
- package/README.md +177 -13
- package/TESTING.md +94 -0
- package/TEST_PLAN.md +21 -4
- package/extensions/delegate-child.ts +1 -0
- package/package.json +4 -3
- package/src/core/common.ts +41 -0
- package/src/core/context/parent-snapshot.ts +142 -0
- package/src/core/context/token-budget.ts +890 -0
- package/src/core/context/visible-conversation-v2.ts +551 -0
- package/src/core/delegate/artifacts.ts +479 -0
- package/src/core/delegate/budget.ts +370 -0
- package/src/core/delegate/hook-contract-evidence.json +18 -0
- package/src/core/delegate/hook-contract.ts +153 -0
- package/src/core/delegate/launch.ts +459 -0
- package/src/core/delegate/result-package.ts +443 -0
- package/src/core/delegate/runner.ts +406 -0
- package/src/core/delegate/seed.ts +411 -0
- package/src/core/delegate/types.ts +304 -0
- package/src/core/fusion/artifacts.ts +15 -0
- package/src/core/fusion/budget.ts +444 -54
- package/src/core/fusion/context.ts +108 -509
- package/src/core/fusion/orchestrator.ts +116 -5
- package/src/core/fusion/prompts.ts +5 -1
- package/src/core/fusion/types.ts +154 -36
- package/src/core/registry.ts +174 -0
- package/src/delegate-child-extension.ts +673 -0
- package/src/delegate-extension.ts +587 -0
- package/src/extension.ts +10 -0
- package/src/fusion-extension.ts +2 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSessionContext,
|
|
3
|
+
convertToLlm,
|
|
4
|
+
type SessionEntry,
|
|
5
|
+
} from '@earendil-works/pi-coding-agent';
|
|
6
|
+
import type { Message } from '@earendil-works/pi-ai';
|
|
7
|
+
import { isJsonObject, type JsonObject } from '../common.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pi session adapter shared by every consumer of the visible-conversation
|
|
11
|
+
* transform.
|
|
12
|
+
*
|
|
13
|
+
* Responsible for exactly one thing: turning the parent's live Pi session into a
|
|
14
|
+
* frozen `Message[]` snapshot, with the in-flight tool call that requested the
|
|
15
|
+
* snapshot (and therefore its sibling calls) excluded from the branch.
|
|
16
|
+
*
|
|
17
|
+
* The transform itself lives in `visible-conversation-v2.ts` and never sees a
|
|
18
|
+
* `SessionManager`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface ReadonlyParentSessionManager {
|
|
22
|
+
getLeafId(): string | null;
|
|
23
|
+
getLeafEntry(): SessionEntry | undefined;
|
|
24
|
+
getEntries(): SessionEntry[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ParentContextSource {
|
|
28
|
+
cwd: string;
|
|
29
|
+
sessionManager: ReadonlyParentSessionManager;
|
|
30
|
+
getSystemPrompt(): string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ParentSnapshotOptions {
|
|
34
|
+
/** Tool call currently executing, when the snapshot is requested from a tool. */
|
|
35
|
+
toolCallId?: string | undefined;
|
|
36
|
+
/** Tool name used for leaf matching when no explicit call id is available. */
|
|
37
|
+
toolName: string;
|
|
38
|
+
/** Commands have no in-flight tool call and therefore never exclude a leaf. */
|
|
39
|
+
excludeActiveToolCallLeaf: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ParentSnapshot {
|
|
43
|
+
messages: readonly Message[];
|
|
44
|
+
leafId: string | null;
|
|
45
|
+
activeToolCallLeafExcluded: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function entriesById(entries: readonly SessionEntry[]): Map<string, SessionEntry> {
|
|
49
|
+
const byId = new Map<string, SessionEntry>();
|
|
50
|
+
for (const entry of entries) byId.set(entry.id, entry);
|
|
51
|
+
return byId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readArray(record: JsonObject, key: string): readonly unknown[] | undefined {
|
|
55
|
+
const value = record[key];
|
|
56
|
+
return Array.isArray(value) ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordOf(value: unknown): JsonObject | undefined {
|
|
60
|
+
if (!isJsonObject(value) || Array.isArray(value)) return undefined;
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function entryMessage(entry: SessionEntry): JsonObject | undefined {
|
|
65
|
+
if (entry.type !== 'message') return undefined;
|
|
66
|
+
return recordOf(entry.message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toolCallPartMatches(
|
|
70
|
+
part: unknown,
|
|
71
|
+
toolCallId: string | undefined,
|
|
72
|
+
toolName: string,
|
|
73
|
+
): boolean {
|
|
74
|
+
const record = recordOf(part);
|
|
75
|
+
if (record === undefined || record['type'] !== 'toolCall') return false;
|
|
76
|
+
if (toolCallId !== undefined) return record['id'] === toolCallId;
|
|
77
|
+
return record['name'] === toolName;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function messageContainsToolCall(
|
|
81
|
+
message: JsonObject,
|
|
82
|
+
toolCallId: string | undefined,
|
|
83
|
+
toolName: string,
|
|
84
|
+
): boolean {
|
|
85
|
+
if (message['role'] !== 'assistant') return false;
|
|
86
|
+
const content = readArray(message, 'content');
|
|
87
|
+
if (content === undefined) return false;
|
|
88
|
+
for (const part of content) {
|
|
89
|
+
if (toolCallPartMatches(part, toolCallId, toolName)) return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface EffectiveLeaf {
|
|
95
|
+
leafId: string | null;
|
|
96
|
+
activeToolCallLeafExcluded: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function effectiveLeafForTool(
|
|
100
|
+
sessionManager: ReadonlyParentSessionManager,
|
|
101
|
+
toolCallId: string | undefined,
|
|
102
|
+
toolName: string,
|
|
103
|
+
): EffectiveLeaf {
|
|
104
|
+
const leaf = sessionManager.getLeafEntry();
|
|
105
|
+
if (leaf === undefined)
|
|
106
|
+
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
107
|
+
const message = entryMessage(leaf);
|
|
108
|
+
if (message !== undefined && messageContainsToolCall(message, toolCallId, toolName)) {
|
|
109
|
+
return { leafId: leaf.parentId, activeToolCallLeafExcluded: true };
|
|
110
|
+
}
|
|
111
|
+
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function resolveEffectiveLeaf(
|
|
115
|
+
sessionManager: ReadonlyParentSessionManager,
|
|
116
|
+
options: ParentSnapshotOptions,
|
|
117
|
+
): EffectiveLeaf {
|
|
118
|
+
if (!options.excludeActiveToolCallLeaf)
|
|
119
|
+
return { leafId: sessionManager.getLeafId(), activeToolCallLeafExcluded: false };
|
|
120
|
+
return effectiveLeafForTool(sessionManager, options.toolCallId, options.toolName);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Freeze the parent conversation into LLM messages.
|
|
125
|
+
*
|
|
126
|
+
* Callers must complete every downstream use of the returned snapshot without
|
|
127
|
+
* re-reading the session, so the seed cannot drift while a child is being
|
|
128
|
+
* launched.
|
|
129
|
+
*/
|
|
130
|
+
export function snapshotParentConversation(
|
|
131
|
+
ctx: ParentContextSource,
|
|
132
|
+
options: ParentSnapshotOptions,
|
|
133
|
+
): ParentSnapshot {
|
|
134
|
+
const entries = ctx.sessionManager.getEntries();
|
|
135
|
+
const leaf = resolveEffectiveLeaf(ctx.sessionManager, options);
|
|
136
|
+
const sessionContext = buildSessionContext(entries, leaf.leafId, entriesById(entries));
|
|
137
|
+
return {
|
|
138
|
+
messages: convertToLlm(sessionContext.messages),
|
|
139
|
+
leafId: leaf.leafId,
|
|
140
|
+
activeToolCallLeafExcluded: leaf.activeToolCallLeafExcluded,
|
|
141
|
+
};
|
|
142
|
+
}
|