@tt-a1i/openpi 0.1.1 → 0.2.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.
Files changed (43) hide show
  1. package/README.md +37 -22
  2. package/SETUP.md +8 -6
  3. package/extensions/ask-user/handoff.ts +5 -1
  4. package/extensions/ask-user/index.ts +44 -0
  5. package/extensions/background-terminals/index.ts +118 -29
  6. package/extensions/background-terminals/src/domain.ts +5 -1
  7. package/extensions/background-terminals/src/manager.ts +2 -1
  8. package/extensions/background-terminals/src/prompt.ts +35 -0
  9. package/extensions/background-terminals/src/result-delivery.ts +76 -3
  10. package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
  11. package/extensions/capabilities/index.ts +198 -0
  12. package/extensions/context-pivot/index.ts +21 -0
  13. package/extensions/cron/index.ts +42 -15
  14. package/extensions/execution-convergence/active-evidence.ts +129 -0
  15. package/extensions/execution-convergence/index.ts +442 -0
  16. package/extensions/execution-convergence/workspace-provenance.ts +338 -0
  17. package/extensions/file-search/index.ts +8 -1
  18. package/extensions/file-search/src/binaries.ts +2 -1
  19. package/extensions/git-info/src/runtime.ts +1 -1
  20. package/extensions/goal/controller.ts +2 -1
  21. package/extensions/goal/index.ts +20 -1
  22. package/extensions/plan-mode/index.ts +12 -0
  23. package/extensions/setup/index.ts +93 -7
  24. package/extensions/shared/child-session.ts +40 -4
  25. package/extensions/shared/setup-config.ts +22 -0
  26. package/extensions/shared/setup-episode-state.ts +7 -0
  27. package/extensions/shared/tool-surface.ts +435 -0
  28. package/extensions/subagents/index.ts +15 -0
  29. package/extensions/subagents/src/manager.ts +13 -11
  30. package/extensions/subagents/src/prompt.ts +1 -1
  31. package/extensions/tasks/index.ts +39 -12
  32. package/extensions/ui-customization/footer.ts +6 -1
  33. package/extensions/workflows/graph-projection.ts +6 -4
  34. package/extensions/workflows/index.ts +16 -1
  35. package/extensions/workflows/invocation-ledger.ts +8 -2
  36. package/extensions/workflows/model.ts +5 -1
  37. package/extensions/workflows/prompt.ts +10 -40
  38. package/extensions/workflows/replay-safety.ts +9 -8
  39. package/package.json +10 -10
  40. package/skills/subagents/SKILL.md +6 -0
  41. package/skills/workflows/EXAMPLES.md +58 -0
  42. package/skills/workflows/REFERENCE.md +44 -0
  43. package/skills/workflows/SKILL.md +39 -0
@@ -10,6 +10,11 @@ import {
10
10
  type SubagentRoleModels,
11
11
  } from "../shared/subagent-roles.ts";
12
12
  import { sanitizeTerminalText } from "../shared/terminal-text.ts";
13
+ import {
14
+ OPENPI_SETUP_EPISODE_CHANNEL,
15
+ type OpenPiSetupEpisodeState,
16
+ } from "../shared/setup-episode-state.ts";
17
+ import { patchOwnedTools } from "../shared/tool-surface.ts";
13
18
  import {
14
19
  formatPiIntercomStatus,
15
20
  inspectPiIntercom,
@@ -18,6 +23,7 @@ import {
18
23
  } from "./intercom.ts";
19
24
  import {
20
25
  applyFooterConfig,
26
+ CAPABILITY_DISCOVERY_MODES,
21
27
  DETAIL_DISPLAYS,
22
28
  FOOTER_ITEMS,
23
29
  FOOTER_LAYOUT_ITEMS,
@@ -33,6 +39,7 @@ import {
33
39
  REASONING_LEVELS,
34
40
  SETUP_CONFIG_CHANGED_CHANNEL,
35
41
  type FooterLayoutItem,
42
+ type CapabilityDiscoveryMode,
36
43
  type FooterPreset,
37
44
  type FooterStyle,
38
45
  type MyPiSetupConfig,
@@ -102,12 +109,12 @@ export function buildInteractiveSetupPrompt(options: {
102
109
  }) {
103
110
  const configurationState = options.savedConfigExists
104
111
  ? [
105
- "This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
112
+ "This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Capability discovery, Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
106
113
  "If the user keeps the current settings, do not call configure_my_pi_setup. If they choose a category, ask only the follow-up needed for that category.",
107
114
  ]
108
115
  : [
109
116
  "This is the first setup. Explain the available choices and their impact in the user's language, then collect the initial preferences.",
110
- "Prefer one ask_user call with up to three independent questions covering Next-action suggestions, Workflow limits, and UI/Footer/result display. Explain that Post-edit defaults off; keep it off unless the user opts in, then ask only for the command. Explain that built-in Agent roles used by subagent_spawn and workflow agent_type inherit the parent model unless the user assigns an available model to a role.",
117
+ "Prefer one ask_user call with up to three independent questions covering Capability discovery plus Workflow limits, Next-action suggestions, and UI/Footer/result display. Explain that Post-edit defaults off; keep it off unless the user opts in, then ask only for the command. Explain that built-in Agent roles used by subagent_spawn and workflow agent_type inherit the parent model unless the user assigns an available model to a role.",
111
118
  ];
112
119
 
113
120
  return [
@@ -122,6 +129,7 @@ export function buildInteractiveSetupPrompt(options: {
122
129
  ...configurationState,
123
130
  "",
124
131
  "Before asking, briefly explain what can be configured and the practical impact:",
132
+ "- Capability discovery: explicit is the safe default and keeps OpenPI model tools absent until the user asks for a capability. adaptive is opt-in and keeps only the small openpi_load_tools gateway visible, allowing the model to load Subagents, Workflows, background terminals, structured search, or Session tracking when it judges them useful. Loaded groups remain session-stable, and normal permission, concurrency, and workflow limits still apply.",
125
133
  "- Next-action suggestions: disabled, or model-generated after a fully settled main-agent run. A suggestion appears as dim inline text on the first row of an empty editor; reserved cells at the row end keep CJK IME preedit from overwriting it. Right accepts it without submitting, and any other editor input dismisses it. Enabling requires an available provider/model and reasoning level and adds one small model call per settled run.",
126
134
  "- Workflow fan-out: concurrency controls simultaneous agents and resource pressure; max agent calls controls the total capacity of one workflow. Valid ranges are 1-64 and 1-1024.",
127
135
  "- UI: the large header costs vertical space; the custom footer is a declarative dashboard. Presets: powerline (default one-line ANSI256 blocks), powerline-mono (one-line high-contrast gray powerline), and compact (one-line plain text). Style can also be set independently: plain, powerline, powerline-mono. Custom lines are a 2D layout of cwd/model/thinking/context/cache/cost/throughput/git/pr plus at most one flex per line for left/right alignment. Nerd Font only affects powerline separator glyphs; text stays readable without it. Changes apply immediately in the active TUI session.",
@@ -132,6 +140,8 @@ export function buildInteractiveSetupPrompt(options: {
132
140
  "- Intercom: optional cross-session messaging is installed only after a native setup confirmation. It stays parent-only; Direct/Workflow children and Replay cannot use it. The status above is informational for this model-guided step—do not install packages or edit its config yourself.",
133
141
  "",
134
142
  "Natural-language configuration examples the user might ask for:",
143
+ '- "let the model discover OpenPI capabilities when useful" → capability_discovery=adaptive',
144
+ '- "only use OpenPI capabilities when I ask" → capability_discovery=explicit',
135
145
  '- "switch footer to powerline" → ui_footer_preset=powerline',
136
146
  '- "use mono powerline" → ui_footer_preset=powerline-mono',
137
147
  '- "compact footer" → ui_footer_preset=compact',
@@ -151,6 +161,16 @@ const safeSetupNotice = (value: unknown, maximum = 500) =>
151
161
  .trim()
152
162
  .slice(0, maximum);
153
163
 
164
+ export function buildSetupSuccessText(
165
+ currentConfiguration: string,
166
+ normalizationNote = "",
167
+ ) {
168
+ return [
169
+ `Updated OpenPI setup. ${currentConfiguration}${normalizationNote}`,
170
+ "This setup episode is complete; configure_my_pi_setup is now hidden. Do not call it again. Do not edit configuration files directly. If the user requests another configuration change, tell them to run /openpi-setup <request> to start a new setup episode.",
171
+ ].join(" ");
172
+ }
173
+
154
174
  export function shouldOfferPiIntercom(options: {
155
175
  readonly request: string;
156
176
  readonly status: PiIntercomStatus;
@@ -231,13 +251,72 @@ async function maybeOfferPiIntercom(
231
251
  }
232
252
  }
233
253
 
254
+ export const CONFIGURE_MY_PI_SETUP_TOOL_NAME = "configure_my_pi_setup";
255
+
256
+ type SetupEpisode = "idle" | "armed" | "active";
257
+
258
+ function showConfigureTool(pi: ExtensionAPI) {
259
+ patchOwnedTools(pi, "setup", {
260
+ enable: [CONFIGURE_MY_PI_SETUP_TOOL_NAME],
261
+ });
262
+ }
263
+
264
+ function hideConfigureTool(pi: ExtensionAPI) {
265
+ patchOwnedTools(pi, "setup", {
266
+ disable: [CONFIGURE_MY_PI_SETUP_TOOL_NAME],
267
+ });
268
+ }
269
+
234
270
  export default function openPiSetup(pi: ExtensionAPI) {
271
+ let episode: SetupEpisode = "idle";
272
+ const publishEpisode = () =>
273
+ pi.events.emit(OPENPI_SETUP_EPISODE_CHANNEL, {
274
+ active: episode !== "idle",
275
+ } satisfies OpenPiSetupEpisodeState);
276
+
277
+ const endEpisode = () => {
278
+ episode = "idle";
279
+ hideConfigureTool(pi);
280
+ publishEpisode();
281
+ };
282
+
283
+ pi.on("session_start", () => {
284
+ endEpisode();
285
+ });
286
+
287
+ pi.on("agent_start", () => {
288
+ if (episode === "armed") {
289
+ episode = "active";
290
+ publishEpisode();
291
+ }
292
+ });
293
+
294
+ pi.on("tool_execution_end", (event) => {
295
+ if (
296
+ episode === "active" &&
297
+ event.toolName === CONFIGURE_MY_PI_SETUP_TOOL_NAME &&
298
+ !event.isError
299
+ ) {
300
+ endEpisode();
301
+ }
302
+ });
303
+
304
+ pi.on("agent_settled", () => {
305
+ if (episode === "active") endEpisode();
306
+ });
307
+
235
308
  pi.registerTool({
236
309
  name: "configure_my_pi_setup",
237
310
  label: "Configure OpenPI",
238
311
  description:
239
- "Apply a user-requested configuration change for this Pi setup. Configures next-action suggestions, workflow fan-out, UI/Footer (presets, style, multi-line layout), result detail display, optional Post-edit, and built-in Agent-role model assignments shared by subagent_spawn and workflow agent_type. Role models must be available in the Pi registry; null clears a role back to parent-model inheritance. Footer examples: powerline preset, powerline-mono, compact, or custom ui_footer_lines with flex. Preserve current values for settings the user did not ask to change. Changes apply immediately to an active TUI footer.",
312
+ "Apply a user-requested configuration change for this Pi setup. Configures capability discovery (explicit or opt-in adaptive), next-action suggestions, workflow fan-out, UI/Footer (presets, style, multi-line layout), result detail display, optional Post-edit, and built-in Agent-role model assignments shared by subagent_spawn and workflow agent_type. Role models must be available in the Pi registry; null clears a role back to parent-model inheritance. Footer examples: powerline preset, powerline-mono, compact, or custom ui_footer_lines with flex. Preserve current values for settings the user did not ask to change. Changes apply immediately to the capability gateway and active TUI footer.",
240
313
  parameters: Type.Object({
314
+ capability_discovery: Type.Optional(
315
+ StringEnum(CAPABILITY_DISCOVERY_MODES, {
316
+ description:
317
+ "Capability adoption policy. explicit keeps OpenPI tools absent until the user asks for a capability; adaptive keeps only the small openpi_load_tools gateway visible so the model may load a useful group on its own. Adaptive can start expensive work such as Subagents or Workflows, so it is opt-in. Omit to preserve the current value.",
318
+ }),
319
+ ),
241
320
  suggestions_enabled: Type.Optional(
242
321
  Type.Boolean({
243
322
  description:
@@ -406,6 +485,12 @@ export default function openPiSetup(pi: ExtensionAPI) {
406
485
  );
407
486
 
408
487
  const config: MyPiSetupConfig = {
488
+ capabilities: {
489
+ discovery:
490
+ (params.capability_discovery as
491
+ | CapabilityDiscoveryMode
492
+ | undefined) ?? current.capabilities.discovery,
493
+ },
409
494
  suggestions: {
410
495
  enabled: suggestionsEnabled,
411
496
  ...(model ? { model } : {}),
@@ -457,9 +542,7 @@ export default function openPiSetup(pi: ExtensionAPI) {
457
542
  : "";
458
543
  if (ctx.hasUI) ctx.ui.notify(`${text}${note}`, "info");
459
544
  return {
460
- content: [
461
- { type: "text", text: `Updated OpenPI setup. ${text}${note}` },
462
- ],
545
+ content: [{ type: "text", text: buildSetupSuccessText(text, note) }],
463
546
  details: config,
464
547
  };
465
548
  },
@@ -490,7 +573,7 @@ export default function openPiSetup(pi: ExtensionAPI) {
490
573
  "Current configuration:",
491
574
  currentConfiguration,
492
575
  "",
493
- "Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session. Intercom installation is handled only by the native setup confirmation; do not install packages or edit its config yourself.",
576
+ "Capability discovery is explicit by default; adaptive is an opt-in that keeps only openpi_load_tools visible so the model may load useful groups. Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session. Intercom installation is handled only by the native setup confirmation; do not install packages or edit its config yourself.",
494
577
  "",
495
578
  "Use configure_my_pi_setup to apply only the requested OpenPI-owned changes and preserve everything else. Interpret model names from the available Pi registry. Do not edit configuration files directly.",
496
579
  ]
@@ -501,6 +584,9 @@ export default function openPiSetup(pi: ExtensionAPI) {
501
584
  savedConfigExists,
502
585
  });
503
586
 
587
+ episode = "armed";
588
+ showConfigureTool(pi);
589
+ publishEpisode();
504
590
  pi.sendUserMessage(
505
591
  prompt.join("\n"),
506
592
  ctx.isIdle() ? undefined : { deliverAs: "followUp" },
@@ -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
- const active = new Set(session.getActiveToolNames());
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
 
@@ -63,6 +63,10 @@ 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
+ export const CAPABILITY_DISCOVERY_MODES = ["explicit", "adaptive"] as const;
67
+ export type CapabilityDiscoveryMode =
68
+ (typeof CAPABILITY_DISCOVERY_MODES)[number];
69
+
66
70
  /** Canonical default layout: one-line Powerline dashboard with flex alignment. */
67
71
  export const DEFAULT_FOOTER_LINES: FooterLines = [
68
72
  [
@@ -123,6 +127,9 @@ export const POST_EDIT_COMMAND_MAX_CHARS = 500;
123
127
  export const SETUP_CONFIG_CHANGED_CHANNEL = "my-pi-setup:config-changed";
124
128
 
125
129
  export interface MyPiSetupConfig {
130
+ readonly capabilities: {
131
+ readonly discovery: CapabilityDiscoveryMode;
132
+ };
126
133
  readonly suggestions: {
127
134
  readonly enabled: boolean;
128
135
  readonly model?: SuggestionModelConfig;
@@ -157,6 +164,7 @@ export interface MyPiSetupConfig {
157
164
  }
158
165
 
159
166
  export const DEFAULT_SETUP_CONFIG: MyPiSetupConfig = {
167
+ capabilities: { discovery: "explicit" },
160
168
  suggestions: { enabled: false },
161
169
  workflows: {
162
170
  concurrency: DEFAULT_WORKFLOW_CONCURRENCY,
@@ -205,6 +213,12 @@ const isFooterStyle = (value: unknown): value is FooterStyle =>
205
213
  const isFooterPreset = (value: unknown): value is FooterPreset =>
206
214
  typeof value === "string" && FOOTER_PRESETS.includes(value as FooterPreset);
207
215
 
216
+ const isCapabilityDiscoveryMode = (
217
+ value: unknown,
218
+ ): value is CapabilityDiscoveryMode =>
219
+ typeof value === "string" &&
220
+ CAPABILITY_DISCOVERY_MODES.includes(value as CapabilityDiscoveryMode);
221
+
208
222
  export function flattenFooterItems(lines: FooterLines): readonly FooterItem[] {
209
223
  const items: FooterItem[] = [];
210
224
  const seen = new Set<FooterItem>();
@@ -401,6 +415,8 @@ function boundedInteger(value: unknown, fallback: number, maximum: number) {
401
415
  export function parseSetupConfig(value: unknown): MyPiSetupConfig {
402
416
  if (!isRecord(value)) return DEFAULT_SETUP_CONFIG;
403
417
 
418
+ const capabilities = isRecord(value.capabilities) ? value.capabilities : {};
419
+
404
420
  // `summaries` is the pre-suggestion config key. Read it once as a migration
405
421
  // source; every subsequent save writes only the canonical `suggestions` key.
406
422
  const suggestions = isRecord(value.suggestions)
@@ -430,6 +446,11 @@ export function parseSetupConfig(value: unknown): MyPiSetupConfig {
430
446
  const subagents = isRecord(value.subagents) ? value.subagents : {};
431
447
  const footer = parseUiFooter(ui);
432
448
  return {
449
+ capabilities: {
450
+ discovery: isCapabilityDiscoveryMode(capabilities.discovery)
451
+ ? capabilities.discovery
452
+ : "explicit",
453
+ },
433
454
  suggestions: {
434
455
  enabled: requestedEnabled && Boolean(model),
435
456
  ...(model ? { model } : {}),
@@ -942,6 +963,7 @@ export function formatSetupConfig(
942
963
  ? `on · ${config.ui.footerStyle} · ${formatFooterLines(config.ui.footerLines)}`
943
964
  : "off";
944
965
  return [
966
+ `Capability discovery: ${config.capabilities.discovery}`,
945
967
  suggestions,
946
968
  `Workflows: ${config.workflows.concurrency} concurrent agents · ${config.workflows.maxAgentCalls} total calls`,
947
969
  `UI: large header ${config.ui.showHeader ? "on" : "off"} · custom footer ${footer}`,
@@ -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
+ }