@nmzpy/pi-ember-stack 0.1.6 → 0.2.2

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 (36) hide show
  1. package/README.md +83 -83
  2. package/package.json +62 -48
  3. package/plugins/devin-auth/extensions/index.ts +68 -7
  4. package/plugins/devin-auth/src/cloud-direct/auth.ts +246 -246
  5. package/plugins/devin-auth/src/cloud-direct/catalog.ts +246 -246
  6. package/plugins/devin-auth/src/cloud-direct/chat.ts +1096 -1091
  7. package/plugins/devin-auth/src/cloud-direct/index.ts +41 -41
  8. package/plugins/devin-auth/src/cloud-direct/metadata.ts +78 -78
  9. package/plugins/devin-auth/src/cloud-direct/wire.ts +202 -202
  10. package/plugins/devin-auth/src/models.ts +42 -196
  11. package/plugins/devin-auth/src/oauth/register-user.ts +174 -174
  12. package/plugins/devin-auth/src/oauth/types.ts +71 -71
  13. package/plugins/devin-auth/src/stream.ts +1 -1
  14. package/plugins/index.ts +29 -6
  15. package/plugins/pi-compact-tools/index.ts +35 -192
  16. package/plugins/pi-compact-tools/renderer.ts +589 -0
  17. package/plugins/pi-custom-agents/index.ts +727 -373
  18. package/plugins/pi-custom-agents/questionnaire-tool.ts +14 -5
  19. package/plugins/pi-custom-agents/subagent/agents/coder.md +1 -0
  20. package/plugins/pi-custom-agents/subagent/agents/scout.md +16 -37
  21. package/plugins/pi-custom-agents/subagent/extensions/agents.ts +18 -1
  22. package/plugins/pi-custom-agents/subagent/extensions/index.ts +118 -226
  23. package/plugins/pi-custom-agents/subagent/extensions/model.ts +96 -96
  24. package/plugins/pi-custom-agents/subagent/extensions/render.ts +205 -1
  25. package/plugins/pi-custom-agents/subagent/extensions/runner.ts +260 -170
  26. package/plugins/pi-custom-agents/subagent/extensions/test/render.test.ts +145 -0
  27. package/plugins/pi-ember-fff/index.ts +975 -0
  28. package/plugins/pi-ember-fff/query.ts +247 -0
  29. package/plugins/pi-ember-fff/test/query.test.ts +222 -0
  30. package/plugins/pi-ember-fff/test/renderer.test.ts +727 -0
  31. package/plugins/pi-ember-tps/index.ts +144 -0
  32. package/plugins/pi-ember-ui/ember.json +99 -0
  33. package/plugins/pi-ember-ui/index.ts +805 -0
  34. package/plugins/pi-ember-ui/mode-colors.ts +196 -0
  35. package/tsconfig.json +2 -1
  36. package/plugins/pi-custom-agents/subagent/agents/architect.md +0 -56
@@ -1,96 +1,96 @@
1
- /**
2
- * Shared model resolution for pi-subagent.
3
- *
4
- * Provides a single canonical resolveModel() used by both the tool handler
5
- * (index.ts) and the event-driven service path (service.ts), ensuring
6
- * consistent error reporting across all sub-agent invocation paths.
7
- *
8
- * Queries the parent ModelRegistry first (catches custom-configured models
9
- * with overridden base URLs, headers, compatibility settings). Falls back
10
- * to the built-in registry for unconfigured models.
11
- * For unqualified names (no provider prefix), known naming conventions
12
- * are tried before assuming Anthropic.
13
- */
14
-
15
- import type { Model } from "@earendil-works/pi-ai";
16
- import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
17
-
18
- type BuiltInModelResolver = (provider: string, id: string) => Model<any> | undefined;
19
-
20
- interface ModelModule {
21
- getModel: BuiltInModelResolver;
22
- }
23
-
24
- // Pi 0.80 exposes getModel from /compat; 0.79 exported it from the main entry.
25
- const { getModel } = (await import("@earendil-works/pi-ai/compat").catch(
26
- () => import("@earendil-works/pi-ai"),
27
- )) as ModelModule;
28
-
29
- export interface ResolvedModel {
30
- model: Model<any> | null;
31
- attempted: string[];
32
- }
33
-
34
- /** Known provider prefixes for unqualified model names. */
35
- const KNOWN_PROVIDERS: [string, RegExp][] = [
36
- ["openai", /^gpt-/i],
37
- ["anthropic", /^claude-/i],
38
- ["google", /^gemini-/i],
39
- ["cohere", /^command-/i],
40
- ["deepseek", /^(deepseek-|ds-)/i],
41
- ["mistral", /^mistral-/i],
42
- ["groq", /^(groq-|llama-)/i],
43
- ];
44
-
45
- function tryGetModel(
46
- provider: string,
47
- id: string,
48
- modelRegistry?: ModelRegistry,
49
- ): Model<any> | null {
50
- // Query parent ModelRegistry first — it includes custom-configured models
51
- // (overridden base URLs, headers, compatibility settings, per-model overrides).
52
- // Fall back to built-in registry for unconfigured models.
53
- if (modelRegistry) {
54
- const found = modelRegistry.find(provider as any, id as any) ?? null;
55
- if (found) return found;
56
- }
57
- const builtIn = getModel(provider as any, id as any) ?? null;
58
- if (builtIn) return builtIn;
59
- return null;
60
- }
61
-
62
- export function resolveModel(
63
- modelName: string | undefined,
64
- parentModel: Model<any> | undefined,
65
- modelRegistry?: ModelRegistry,
66
- ): ResolvedModel {
67
- const attempted: string[] = [];
68
- if (modelName) {
69
- const idx = modelName.indexOf("/");
70
- if (idx > 0) {
71
- // Provider-qualified: "openai/gpt-4o" or "openrouter/anthropic/claude-3.5"
72
- const provider = modelName.slice(0, idx);
73
- const id = modelName.slice(idx + 1);
74
- attempted.push(modelName);
75
- const found = tryGetModel(provider, id, modelRegistry);
76
- if (found) return { model: found, attempted };
77
- } else {
78
- // Unqualified: try known providers by naming convention
79
- for (const [provider, pattern] of KNOWN_PROVIDERS) {
80
- if (pattern.test(modelName)) {
81
- attempted.push(`${provider}/${modelName}`);
82
- const found = tryGetModel(provider, modelName, modelRegistry);
83
- if (found) return { model: found, attempted };
84
- }
85
- }
86
- // Fall back to Anthropic shorthand (backward compat)
87
- attempted.push(`anthropic/${modelName}`);
88
- const found = tryGetModel("anthropic", modelName, modelRegistry);
89
- if (found) return { model: found, attempted };
90
- }
91
- } else if (parentModel) {
92
- attempted.push(`${parentModel.provider}/${parentModel.id}`);
93
- return { model: parentModel, attempted };
94
- }
95
- return { model: null, attempted };
96
- }
1
+ /**
2
+ * Shared model resolution for pi-subagent.
3
+ *
4
+ * Provides a single canonical resolveModel() used by both the tool handler
5
+ * (index.ts) and the event-driven service path (service.ts), ensuring
6
+ * consistent error reporting across all sub-agent invocation paths.
7
+ *
8
+ * Queries the parent ModelRegistry first (catches custom-configured models
9
+ * with overridden base URLs, headers, compatibility settings). Falls back
10
+ * to the built-in registry for unconfigured models.
11
+ * For unqualified names (no provider prefix), known naming conventions
12
+ * are tried before assuming Anthropic.
13
+ */
14
+
15
+ import type { Model } from "@earendil-works/pi-ai";
16
+ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
17
+
18
+ type BuiltInModelResolver = (provider: string, id: string) => Model<any> | undefined;
19
+
20
+ interface ModelModule {
21
+ getModel: BuiltInModelResolver;
22
+ }
23
+
24
+ // Pi 0.80 exposes getModel from /compat; 0.79 exported it from the main entry.
25
+ const { getModel } = (await import("@earendil-works/pi-ai/compat").catch(
26
+ () => import("@earendil-works/pi-ai"),
27
+ )) as ModelModule;
28
+
29
+ export interface ResolvedModel {
30
+ model: Model<any> | null;
31
+ attempted: string[];
32
+ }
33
+
34
+ /** Known provider prefixes for unqualified model names. */
35
+ const KNOWN_PROVIDERS: [string, RegExp][] = [
36
+ ["openai", /^gpt-/i],
37
+ ["anthropic", /^claude-/i],
38
+ ["google", /^gemini-/i],
39
+ ["cohere", /^command-/i],
40
+ ["deepseek", /^(deepseek-|ds-)/i],
41
+ ["mistral", /^mistral-/i],
42
+ ["groq", /^(groq-|llama-)/i],
43
+ ];
44
+
45
+ function tryGetModel(
46
+ provider: string,
47
+ id: string,
48
+ modelRegistry?: ModelRegistry,
49
+ ): Model<any> | null {
50
+ // Query parent ModelRegistry first — it includes custom-configured models
51
+ // (overridden base URLs, headers, compatibility settings, per-model overrides).
52
+ // Fall back to built-in registry for unconfigured models.
53
+ if (modelRegistry) {
54
+ const found = modelRegistry.find(provider as any, id as any) ?? null;
55
+ if (found) return found;
56
+ }
57
+ const builtIn = getModel(provider as any, id as any) ?? null;
58
+ if (builtIn) return builtIn;
59
+ return null;
60
+ }
61
+
62
+ export function resolveModel(
63
+ modelName: string | undefined,
64
+ parentModel: Model<any> | undefined,
65
+ modelRegistry?: ModelRegistry,
66
+ ): ResolvedModel {
67
+ const attempted: string[] = [];
68
+ if (modelName) {
69
+ const idx = modelName.indexOf("/");
70
+ if (idx > 0) {
71
+ // Provider-qualified: "openai/gpt-4o" or "openrouter/anthropic/claude-3.5"
72
+ const provider = modelName.slice(0, idx);
73
+ const id = modelName.slice(idx + 1);
74
+ attempted.push(modelName);
75
+ const found = tryGetModel(provider, id, modelRegistry);
76
+ if (found) return { model: found, attempted };
77
+ } else {
78
+ // Unqualified: try known providers by naming convention
79
+ for (const [provider, pattern] of KNOWN_PROVIDERS) {
80
+ if (pattern.test(modelName)) {
81
+ attempted.push(`${provider}/${modelName}`);
82
+ const found = tryGetModel(provider, modelName, modelRegistry);
83
+ if (found) return { model: found, attempted };
84
+ }
85
+ }
86
+ // Fall back to Anthropic shorthand (backward compat)
87
+ attempted.push(`anthropic/${modelName}`);
88
+ const found = tryGetModel("anthropic", modelName, modelRegistry);
89
+ if (found) return { model: found, attempted };
90
+ }
91
+ } else if (parentModel) {
92
+ attempted.push(`${parentModel.provider}/${parentModel.id}`);
93
+ return { model: parentModel, attempted };
94
+ }
95
+ return { model: null, attempted };
96
+ }
@@ -8,10 +8,40 @@
8
8
 
9
9
  import * as os from "node:os";
10
10
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
11
- import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
11
+ import { renderGradientLabel } from "../../../pi-ember-ui/index.ts";
12
+ import { getActiveModeColor } from "../../../pi-ember-ui/mode-colors.ts";
13
+ import { Box, Container, type Component, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
12
14
  import type { Message } from "@earendil-works/pi-ai";
13
15
  import { type SubAgentResult, isFailedResult, getResultOutput } from "./runner.ts";
14
16
 
17
+ type Foreground = (color: string, text: string) => string;
18
+
19
+ /**
20
+ * Width-aware cap for the running subagent shell. It renders at the width
21
+ * supplied by the TUI instead of baking a terminal width into the component.
22
+ */
23
+ export class SubagentCapLine implements Component {
24
+ private foreground: Foreground;
25
+
26
+ constructor(
27
+ private readonly isVisible: () => boolean,
28
+ foreground: Foreground,
29
+ ) {
30
+ this.foreground = foreground;
31
+ }
32
+
33
+ setForeground(foreground: Foreground): void {
34
+ this.foreground = foreground;
35
+ }
36
+
37
+ render(width: number): string[] {
38
+ if (!this.isVisible()) return [];
39
+ return [this.foreground("border", "\u2500".repeat(Math.max(0, width)))];
40
+ }
41
+
42
+ invalidate(): void {}
43
+ }
44
+
15
45
  // ---------------------------------------------------------------------------
16
46
  // Safe type guards
17
47
  // ---------------------------------------------------------------------------
@@ -276,3 +306,177 @@ export function aggregateUsage(results: SubAgentResult[]) {
276
306
  }
277
307
  return total;
278
308
  }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // Compact grouped layout (Exploring-style)
312
+ // ---------------------------------------------------------------------------
313
+
314
+ /**
315
+ * Per-agent status derived from a SubAgentResult.
316
+ * `exitCode === -1` means still running (no result yet).
317
+ */
318
+ type AgentStatus = "running" | "completed" | "failed";
319
+
320
+ function agentStatus(result: SubAgentResult | undefined): AgentStatus {
321
+ if (!result || result.exitCode === -1) return "running";
322
+ return isFailedResult(result) ? "failed" : "completed";
323
+ }
324
+
325
+ function agentIcon(status: AgentStatus, theme: any): string {
326
+ if (status === "failed") return theme.fg("error", "✗ ");
327
+ return theme.fg("success", "✓ ");
328
+ }
329
+
330
+ function hashPhase(name: string): number {
331
+ let n = 7;
332
+ for (const ch of name) n = (n * 31 + ch.charCodeAt(0)) >>> 0;
333
+ return (n % 1000) / 1000;
334
+ }
335
+
336
+ function renderAgentLabel(status: AgentStatus, agentName: string, theme: any, result?: SubAgentResult): string {
337
+ if (status === "running") return renderGradientLabel(agentName, getActiveModeColor(), hashPhase(agentName));
338
+ let suffix = "";
339
+ if (status === "failed" && result) {
340
+ const output = getResultOutput(result).trim();
341
+ if (output) {
342
+ const clipped = output.length > 60 ? `${output.slice(0, 60)}...` : output;
343
+ suffix = ` ${theme.fg("muted", clipped)}`;
344
+ }
345
+ }
346
+ return agentIcon(status, theme) + theme.fg("accent", agentName) + suffix;
347
+ }
348
+
349
+ function renderGroupLabel(
350
+ label: string,
351
+ _hasError: boolean,
352
+ _allDone: boolean,
353
+ theme: any,
354
+ ): string {
355
+ // Header is always plain dim/bold; never gradient.
356
+ return theme.fg("dim", theme.bold(label));
357
+ }
358
+
359
+ /**
360
+ * Render the compact grouped layout for a subagent tool call.
361
+ *
362
+ * - Single mode: running `agentName` uses the Thinking gradient; completed
363
+ * and failed agents use green/red bullets.
364
+ * - Parallel mode: `Subagents` header + `└ agent` children with the same
365
+ * running/completed/failed treatment.
366
+ * - Chain mode: same grouped structure, but only running + completed steps
367
+ * appear (pending steps are hidden until they start).
368
+ *
369
+ * No `⏳`, no `[scope]`, no `parallel (N tasks)` — just bullets and names.
370
+ */
371
+ export function renderSubagentLayout(
372
+ args: any,
373
+ results: SubAgentResult[],
374
+ theme: any,
375
+ ): string {
376
+ const fg = theme.fg.bind(theme);
377
+
378
+ // --- Single mode ---
379
+ if (args.agent && args.task && !(args.tasks?.length > 0) && !(args.chain?.length > 0)) {
380
+ const status = agentStatus(results[0]);
381
+ return renderAgentLabel(status, args.agent, theme, results[0]);
382
+ }
383
+
384
+ // --- Parallel mode ---
385
+ if (args.tasks && args.tasks.length > 0) {
386
+ const tasks = args.tasks as Array<{ agent: string }>;
387
+ const statuses = tasks.map((_, i) => agentStatus(results[i]));
388
+ const hasError = statuses.some((s) => s === "failed");
389
+ const allDone = statuses.every((s) => s !== "running");
390
+ const lines = [renderGroupLabel("Subagents", hasError, allDone, theme)];
391
+ for (const [i, t] of tasks.entries()) {
392
+ const prefix = i === 0 ? " └ " : " ";
393
+ lines.push(fg("dim", prefix) + renderAgentLabel(statuses[i], t.agent, theme, results[i]));
394
+ }
395
+ return lines.join("\n");
396
+ }
397
+
398
+ // --- Chain mode ---
399
+ if (args.chain && args.chain.length > 0) {
400
+ const chain = args.chain as Array<{ agent: string }>;
401
+ // Only show steps that have started (have a result entry).
402
+ const started = chain.slice(0, results.length);
403
+ const statuses = started.map((_, i) => agentStatus(results[i]));
404
+ const hasError = statuses.some((s) => s === "failed");
405
+ const allDone = statuses.length > 0 && statuses.every((s) => s !== "running");
406
+ const lines = [renderGroupLabel("Subagents", hasError, allDone, theme)];
407
+ for (const [i, step] of started.entries()) {
408
+ const prefix = i === 0 ? " └ " : " ";
409
+ lines.push(fg("dim", prefix) + renderAgentLabel(statuses[i], step.agent, theme, results[i]));
410
+ }
411
+ return lines.join("\n");
412
+ }
413
+
414
+ // Fallback (should not reach here)
415
+ return fg("dim", "subagent");
416
+ }
417
+
418
+ /**
419
+ * Whether any agent in the layout is still running (flashing).
420
+ */
421
+ export function anySubagentRunning(args: any, results: SubAgentResult[]): boolean {
422
+ if (args.agent && args.task && !(args.tasks?.length > 0) && !(args.chain?.length > 0)) {
423
+ return agentStatus(results[0]) === "running";
424
+ }
425
+ if (args.tasks && args.tasks.length > 0) {
426
+ return args.tasks.some((_t: any, i: number) => agentStatus(results[i]) === "running");
427
+ }
428
+ if (args.chain && args.chain.length > 0) {
429
+ return args.chain.slice(0, results.length).some((_s: any, i: number) => agentStatus(results[i]) === "running");
430
+ }
431
+ return false;
432
+ }
433
+
434
+ // ---------------------------------------------------------------------------
435
+ // Expanded view (Ctrl+O)
436
+ // ---------------------------------------------------------------------------
437
+
438
+ /**
439
+ * Detailed per-agent output for the expanded view, wrapped in a
440
+ * subagentBg Box so it stays visually integrated with the collapsed row.
441
+ */
442
+ export function renderSubagentExpanded(
443
+ details: { mode: "single" | "parallel" | "chain"; results: SubAgentResult[] },
444
+ theme: any,
445
+ ): Component | undefined {
446
+ const fg = theme.fg.bind(theme);
447
+ const mdTheme = getMarkdownTheme();
448
+ const box = new Box(1, 0, (s: string) => (theme.bg as any)("subagentBg", s));
449
+
450
+ if (details.mode === "single" && details.results.length === 1) {
451
+ const inner = renderSingleResult(details.results[0], true, theme);
452
+ if (inner instanceof Container) {
453
+ box.addChild(inner);
454
+ } else if (inner instanceof Text) {
455
+ box.addChild(inner);
456
+ }
457
+ return box;
458
+ }
459
+
460
+ const container = new Container();
461
+ for (const r of details.results) {
462
+ const stepIcon = isFailedResult(r) ? fg("error", "✗") : fg("success", "✓");
463
+ container.addChild(new Text(`${stepIcon} ${fg("accent", r.agent)}`, 0, 0));
464
+ if (r.errorMessage) {
465
+ container.addChild(new Text(fg("error", `Error: ${r.errorMessage}`), 0, 0));
466
+ }
467
+ const finalOutput = getResultOutput(r);
468
+ if (finalOutput) {
469
+ container.addChild(new Spacer(1));
470
+ container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
471
+ }
472
+ const usageStr = formatUsageStats(r.usage, r.model);
473
+ if (usageStr) container.addChild(new Text(fg("dim", usageStr), 0, 0));
474
+ container.addChild(new Spacer(1));
475
+ }
476
+ const totalUsage = formatUsageStats(aggregateUsage(details.results));
477
+ if (totalUsage) {
478
+ container.addChild(new Text(fg("dim", `Total: ${totalUsage}`), 0, 0));
479
+ }
480
+ box.addChild(container);
481
+ return box;
482
+ }