@tt-a1i/openpi 0.1.1 → 0.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/README.md +65 -28
- package/SETUP.md +8 -6
- package/extensions/ask-user/handoff.ts +5 -1
- package/extensions/ask-user/index.ts +44 -0
- package/extensions/background-terminals/index.ts +118 -29
- package/extensions/background-terminals/src/domain.ts +5 -1
- package/extensions/background-terminals/src/manager.ts +2 -1
- package/extensions/background-terminals/src/prompt.ts +35 -0
- package/extensions/background-terminals/src/result-delivery.ts +76 -3
- package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
- package/extensions/capabilities/index.ts +198 -0
- package/extensions/context-pivot/index.ts +21 -0
- package/extensions/cron/index.ts +42 -15
- package/extensions/execution-convergence/active-evidence.ts +129 -0
- package/extensions/execution-convergence/index.ts +442 -0
- package/extensions/execution-convergence/workspace-provenance.ts +338 -0
- package/extensions/file-search/index.ts +8 -1
- package/extensions/file-search/src/binaries.ts +2 -1
- package/extensions/git-info/src/runtime.ts +1 -1
- package/extensions/goal/controller.ts +2 -1
- package/extensions/goal/index.ts +20 -1
- package/extensions/plan-mode/bash-policy.ts +219 -42
- package/extensions/plan-mode/index.ts +56 -19
- package/extensions/setup/index.ts +96 -10
- package/extensions/shared/child-session.ts +40 -4
- package/extensions/shared/editor-layers.ts +150 -0
- package/extensions/shared/setup-config.ts +26 -15
- package/extensions/shared/setup-episode-state.ts +7 -0
- package/extensions/shared/tool-surface.ts +435 -0
- package/extensions/subagents/index.ts +179 -96
- package/extensions/subagents/src/manager.ts +13 -11
- package/extensions/subagents/src/prompt.ts +7 -7
- package/extensions/subagents/src/ui/takeover.ts +231 -133
- package/extensions/subagents/src/ui/transcript.ts +252 -37
- package/extensions/subagents/src/ui/wait-result.ts +6 -19
- package/extensions/suggestions/index.ts +27 -18
- package/extensions/tasks/index.ts +63 -18
- package/extensions/ui-customization/footer.ts +65 -11
- package/extensions/workflows/graph-projection.ts +6 -4
- package/extensions/workflows/index.ts +44 -21
- package/extensions/workflows/invocation-ledger.ts +8 -2
- package/extensions/workflows/model.ts +5 -1
- package/extensions/workflows/prompt.ts +10 -40
- package/extensions/workflows/replay-safety.ts +9 -8
- package/package.json +10 -10
- package/skills/subagents/SKILL.md +7 -1
- package/skills/workflows/EXAMPLES.md +58 -0
- package/skills/workflows/REFERENCE.md +44 -0
- package/skills/workflows/SKILL.md +39 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import {
|
|
3
|
+
type AgentSession,
|
|
3
4
|
DefaultResourceLoader,
|
|
4
5
|
getAgentDir,
|
|
5
6
|
ProjectTrustStore,
|
|
6
|
-
SettingsManager,
|
|
7
|
-
type AgentSession,
|
|
8
7
|
type SessionShutdownEvent,
|
|
8
|
+
SettingsManager,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
|
|
11
11
|
export const CHILD_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
@@ -51,6 +51,8 @@ function isPiIntercomNpmResource(resource: {
|
|
|
51
51
|
* drift test in child-session.test.ts).
|
|
52
52
|
*/
|
|
53
53
|
export const CHILD_EXCLUDED_TOOL_NAMES = [
|
|
54
|
+
// capability discovery mutates the parent model-facing tool surface
|
|
55
|
+
"openpi_load_tools",
|
|
54
56
|
// subagents — children cannot spawn/observe more agents
|
|
55
57
|
"subagent_spawn",
|
|
56
58
|
"subagent_wait",
|
|
@@ -185,7 +187,9 @@ export function resolveStandaloneChildProjectTrust(options: {
|
|
|
185
187
|
|
|
186
188
|
interface ChildSessionStartup {
|
|
187
189
|
bindExtensions(bindings: { mode: "print" }): Promise<void>;
|
|
188
|
-
getActiveToolNames(): string[];
|
|
190
|
+
getActiveToolNames?(): string[];
|
|
191
|
+
getAllTools?(): { name: string }[];
|
|
192
|
+
setActiveToolsByName?(toolNames: string[]): void;
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
function boundedToolNames(names: readonly string[]) {
|
|
@@ -208,9 +212,41 @@ export async function bindChildSessionExtensions(
|
|
|
208
212
|
) {
|
|
209
213
|
await session.bindExtensions({ mode: "print" });
|
|
210
214
|
const requested = effectiveChildToolAllowlist(requestedTools);
|
|
215
|
+
let active: Set<string> | undefined;
|
|
216
|
+
if (
|
|
217
|
+
session.getActiveToolNames &&
|
|
218
|
+
session.getAllTools &&
|
|
219
|
+
session.setActiveToolsByName
|
|
220
|
+
) {
|
|
221
|
+
const requestedSet = requested ? new Set(requested) : undefined;
|
|
222
|
+
const available = new Set(session.getAllTools().map(({ name }) => name));
|
|
223
|
+
const activeNames = session.getActiveToolNames();
|
|
224
|
+
active = new Set(activeNames);
|
|
225
|
+
for (const name of CHILD_SAFE_PACKAGE_TOOL_NAMES) {
|
|
226
|
+
if (
|
|
227
|
+
available.has(name) &&
|
|
228
|
+
!active.has(name) &&
|
|
229
|
+
(requestedSet === undefined || requestedSet.has(name))
|
|
230
|
+
) {
|
|
231
|
+
activeNames.push(name);
|
|
232
|
+
active.add(name);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (activeNames.length !== session.getActiveToolNames().length) {
|
|
236
|
+
session.setActiveToolsByName(activeNames);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
211
239
|
if (!requested) return;
|
|
212
240
|
|
|
213
|
-
|
|
241
|
+
if (!active && session.getActiveToolNames) {
|
|
242
|
+
active = new Set(session.getActiveToolNames());
|
|
243
|
+
}
|
|
244
|
+
if (!active) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
"Child tool preflight failed: the bound child session does not expose active-tool introspection.",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
214
250
|
const missing = [...new Set(requested)].filter((name) => !active.has(name));
|
|
215
251
|
if (missing.length === 0) return;
|
|
216
252
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CustomEditor,
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
type KeybindingsManager,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { EditorComponent, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
8
|
+
|
|
9
|
+
const CLAIM_CHANNEL = "openpi:editor-layers:claim";
|
|
10
|
+
const REGISTER_CHANNEL = "openpi:editor-layers:register";
|
|
11
|
+
const REMOVE_CHANNEL = "openpi:editor-layers:remove";
|
|
12
|
+
|
|
13
|
+
type EditorFactory = NonNullable<
|
|
14
|
+
ReturnType<ExtensionContext["ui"]["getEditorComponent"]>
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
export interface EditorLayer {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly order: number;
|
|
20
|
+
readonly wrap: (
|
|
21
|
+
base: EditorComponent,
|
|
22
|
+
tui: TUI,
|
|
23
|
+
theme: EditorTheme,
|
|
24
|
+
keybindings: KeybindingsManager,
|
|
25
|
+
) => EditorComponent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface EditorLayerRegistration {
|
|
29
|
+
readonly ctx: ExtensionContext;
|
|
30
|
+
readonly layer: EditorLayer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
34
|
+
return typeof value === "object" && value !== null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readRegistration(value: unknown) {
|
|
38
|
+
if (!isRecord(value) || !isRecord(value.layer)) return undefined;
|
|
39
|
+
const { ctx, layer } = value;
|
|
40
|
+
if (
|
|
41
|
+
!isRecord(ctx) ||
|
|
42
|
+
typeof layer.id !== "string" ||
|
|
43
|
+
typeof layer.order !== "number" ||
|
|
44
|
+
typeof layer.wrap !== "function"
|
|
45
|
+
) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ctx: ctx as unknown as ExtensionContext,
|
|
50
|
+
layer: layer as unknown as EditorLayer,
|
|
51
|
+
} satisfies EditorLayerRegistration;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readLayerId(value: unknown) {
|
|
55
|
+
if (!isRecord(value) || typeof value.id !== "string") return undefined;
|
|
56
|
+
return value.id;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function composeEditorFactory(
|
|
60
|
+
previous: EditorFactory | undefined,
|
|
61
|
+
layers: readonly EditorLayer[],
|
|
62
|
+
) {
|
|
63
|
+
return ((tui, theme, keybindings) => {
|
|
64
|
+
let editor =
|
|
65
|
+
previous?.(tui, theme, keybindings) ??
|
|
66
|
+
new CustomEditor(tui, theme, keybindings);
|
|
67
|
+
for (const layer of layers) {
|
|
68
|
+
editor = layer.wrap(editor, tui, theme, keybindings);
|
|
69
|
+
}
|
|
70
|
+
return editor;
|
|
71
|
+
}) satisfies EditorFactory;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Each extension is evaluated in its own jiti module graph, so ordinary module
|
|
76
|
+
* singletons are not shared. The first OpenPI editor contributor claims the
|
|
77
|
+
* runtime EventBus and coordinates the rest through that host-owned boundary.
|
|
78
|
+
*/
|
|
79
|
+
function ensureCoordinator(pi: ExtensionAPI) {
|
|
80
|
+
const claim = { claimed: false };
|
|
81
|
+
pi.events.emit(CLAIM_CHANNEL, claim);
|
|
82
|
+
if (claim.claimed) return;
|
|
83
|
+
|
|
84
|
+
let ctx: ExtensionContext | undefined;
|
|
85
|
+
let installTimer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
const layers = new Map<string, EditorLayer>();
|
|
87
|
+
|
|
88
|
+
const cancelInstall = () => {
|
|
89
|
+
if (installTimer) clearTimeout(installTimer);
|
|
90
|
+
installTimer = undefined;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const install = () => {
|
|
94
|
+
installTimer = undefined;
|
|
95
|
+
const current = ctx;
|
|
96
|
+
if (!current || current.mode !== "tui" || layers.size === 0) return;
|
|
97
|
+
const ordered = [...layers.values()].sort(
|
|
98
|
+
(left, right) =>
|
|
99
|
+
left.order - right.order || left.id.localeCompare(right.id),
|
|
100
|
+
);
|
|
101
|
+
current.ui.setEditorComponent(
|
|
102
|
+
composeEditorFactory(current.ui.getEditorComponent(), ordered),
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const scheduleInstall = () => {
|
|
107
|
+
if (installTimer) return;
|
|
108
|
+
installTimer = setTimeout(install, 0);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
pi.events.on(CLAIM_CHANNEL, (value) => {
|
|
112
|
+
if (isRecord(value) && value.claimed === false) value.claimed = true;
|
|
113
|
+
});
|
|
114
|
+
pi.events.on(REGISTER_CHANNEL, (value) => {
|
|
115
|
+
const registration = readRegistration(value);
|
|
116
|
+
if (!registration || registration.ctx.mode !== "tui") return;
|
|
117
|
+
if (ctx !== registration.ctx) {
|
|
118
|
+
cancelInstall();
|
|
119
|
+
layers.clear();
|
|
120
|
+
ctx = registration.ctx;
|
|
121
|
+
}
|
|
122
|
+
layers.set(registration.layer.id, registration.layer);
|
|
123
|
+
scheduleInstall();
|
|
124
|
+
});
|
|
125
|
+
pi.events.on(REMOVE_CHANNEL, (value) => {
|
|
126
|
+
const id = readLayerId(value);
|
|
127
|
+
if (!id) return;
|
|
128
|
+
layers.delete(id);
|
|
129
|
+
if (layers.size > 0) return;
|
|
130
|
+
cancelInstall();
|
|
131
|
+
ctx = undefined;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function registerEditorLayer(
|
|
136
|
+
pi: ExtensionAPI,
|
|
137
|
+
ctx: ExtensionContext,
|
|
138
|
+
layer: EditorLayer,
|
|
139
|
+
) {
|
|
140
|
+
if (ctx.mode !== "tui") return;
|
|
141
|
+
ensureCoordinator(pi);
|
|
142
|
+
pi.events.emit(REGISTER_CHANNEL, {
|
|
143
|
+
ctx,
|
|
144
|
+
layer,
|
|
145
|
+
} satisfies EditorLayerRegistration);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function removeEditorLayer(pi: ExtensionAPI, id: string) {
|
|
149
|
+
pi.events.emit(REMOVE_CHANNEL, { id });
|
|
150
|
+
}
|
|
@@ -63,23 +63,16 @@ export type FooterLines = readonly (readonly FooterLayoutItem[])[];
|
|
|
63
63
|
export const DETAIL_DISPLAYS = ["full", "compact"] as const;
|
|
64
64
|
export type DetailDisplay = (typeof DETAIL_DISPLAYS)[number];
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
export const CAPABILITY_DISCOVERY_MODES = ["explicit", "adaptive"] as const;
|
|
67
|
+
export type CapabilityDiscoveryMode =
|
|
68
|
+
(typeof CAPABILITY_DISCOVERY_MODES)[number];
|
|
69
|
+
|
|
70
|
+
/** Canonical default layout: one-line plain footer with flex alignment. */
|
|
67
71
|
export const DEFAULT_FOOTER_LINES: FooterLines = [
|
|
68
|
-
[
|
|
69
|
-
"cwd",
|
|
70
|
-
"model",
|
|
71
|
-
"thinking",
|
|
72
|
-
"context",
|
|
73
|
-
"cache",
|
|
74
|
-
"cost",
|
|
75
|
-
"throughput",
|
|
76
|
-
"flex",
|
|
77
|
-
"git",
|
|
78
|
-
"pr",
|
|
79
|
-
],
|
|
72
|
+
["cwd", "git", "pr", "flex", "model", "context", "cost"],
|
|
80
73
|
];
|
|
81
74
|
|
|
82
|
-
export const DEFAULT_FOOTER_STYLE: FooterStyle = "
|
|
75
|
+
export const DEFAULT_FOOTER_STYLE: FooterStyle = "plain";
|
|
83
76
|
|
|
84
77
|
export const DEFAULT_FOOTER_ITEMS: readonly FooterItem[] =
|
|
85
78
|
flattenFooterItems(DEFAULT_FOOTER_LINES);
|
|
@@ -123,6 +116,9 @@ export const POST_EDIT_COMMAND_MAX_CHARS = 500;
|
|
|
123
116
|
export const SETUP_CONFIG_CHANGED_CHANNEL = "my-pi-setup:config-changed";
|
|
124
117
|
|
|
125
118
|
export interface MyPiSetupConfig {
|
|
119
|
+
readonly capabilities: {
|
|
120
|
+
readonly discovery: CapabilityDiscoveryMode;
|
|
121
|
+
};
|
|
126
122
|
readonly suggestions: {
|
|
127
123
|
readonly enabled: boolean;
|
|
128
124
|
readonly model?: SuggestionModelConfig;
|
|
@@ -157,6 +153,7 @@ export interface MyPiSetupConfig {
|
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
export const DEFAULT_SETUP_CONFIG: MyPiSetupConfig = {
|
|
156
|
+
capabilities: { discovery: "explicit" },
|
|
160
157
|
suggestions: { enabled: false },
|
|
161
158
|
workflows: {
|
|
162
159
|
concurrency: DEFAULT_WORKFLOW_CONCURRENCY,
|
|
@@ -205,6 +202,12 @@ const isFooterStyle = (value: unknown): value is FooterStyle =>
|
|
|
205
202
|
const isFooterPreset = (value: unknown): value is FooterPreset =>
|
|
206
203
|
typeof value === "string" && FOOTER_PRESETS.includes(value as FooterPreset);
|
|
207
204
|
|
|
205
|
+
const isCapabilityDiscoveryMode = (
|
|
206
|
+
value: unknown,
|
|
207
|
+
): value is CapabilityDiscoveryMode =>
|
|
208
|
+
typeof value === "string" &&
|
|
209
|
+
CAPABILITY_DISCOVERY_MODES.includes(value as CapabilityDiscoveryMode);
|
|
210
|
+
|
|
208
211
|
export function flattenFooterItems(lines: FooterLines): readonly FooterItem[] {
|
|
209
212
|
const items: FooterItem[] = [];
|
|
210
213
|
const seen = new Set<FooterItem>();
|
|
@@ -401,6 +404,8 @@ function boundedInteger(value: unknown, fallback: number, maximum: number) {
|
|
|
401
404
|
export function parseSetupConfig(value: unknown): MyPiSetupConfig {
|
|
402
405
|
if (!isRecord(value)) return DEFAULT_SETUP_CONFIG;
|
|
403
406
|
|
|
407
|
+
const capabilities = isRecord(value.capabilities) ? value.capabilities : {};
|
|
408
|
+
|
|
404
409
|
// `summaries` is the pre-suggestion config key. Read it once as a migration
|
|
405
410
|
// source; every subsequent save writes only the canonical `suggestions` key.
|
|
406
411
|
const suggestions = isRecord(value.suggestions)
|
|
@@ -430,6 +435,11 @@ export function parseSetupConfig(value: unknown): MyPiSetupConfig {
|
|
|
430
435
|
const subagents = isRecord(value.subagents) ? value.subagents : {};
|
|
431
436
|
const footer = parseUiFooter(ui);
|
|
432
437
|
return {
|
|
438
|
+
capabilities: {
|
|
439
|
+
discovery: isCapabilityDiscoveryMode(capabilities.discovery)
|
|
440
|
+
? capabilities.discovery
|
|
441
|
+
: "explicit",
|
|
442
|
+
},
|
|
433
443
|
suggestions: {
|
|
434
444
|
enabled: requestedEnabled && Boolean(model),
|
|
435
445
|
...(model ? { model } : {}),
|
|
@@ -942,10 +952,11 @@ export function formatSetupConfig(
|
|
|
942
952
|
? `on · ${config.ui.footerStyle} · ${formatFooterLines(config.ui.footerLines)}`
|
|
943
953
|
: "off";
|
|
944
954
|
return [
|
|
955
|
+
`Capability discovery: ${config.capabilities.discovery}`,
|
|
945
956
|
suggestions,
|
|
946
957
|
`Workflows: ${config.workflows.concurrency} concurrent agents · ${config.workflows.maxAgentCalls} total calls`,
|
|
947
958
|
`UI: large header ${config.ui.showHeader ? "on" : "off"} · custom footer ${footer}`,
|
|
948
|
-
`Subagent results: ${config.ui.subagentResultDisplay === "full" ? "full by default" : "compact
|
|
959
|
+
`Subagent results: ${config.ui.subagentResultDisplay === "full" ? "full by default" : "compact status summary (Ctrl+O expands full output)"}`,
|
|
949
960
|
`Bash operations: ${config.ui.bashToolDisplay === "full" ? "expanded by default" : "folded preview (Ctrl+O expands all)"}`,
|
|
950
961
|
`Write/Edit operations: ${config.ui.fileMutationDisplay === "full" ? "expanded by default" : "folded preview (Ctrl+O expands all)"}`,
|
|
951
962
|
`Post-edit command: ${config.postEdit.command ? config.postEdit.command : "off"}`,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Broadcast whenever the package-owned setup episode becomes usable or ends. */
|
|
2
|
+
export const OPENPI_SETUP_EPISODE_CHANNEL = "openpi:setup-episode";
|
|
3
|
+
|
|
4
|
+
export interface OpenPiSetupEpisodeState {
|
|
5
|
+
/** True for both armed and actively running setup episodes. */
|
|
6
|
+
readonly active: boolean;
|
|
7
|
+
}
|