@nmzpy/pi-ember-stack 0.2.1 → 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 (30) hide show
  1. package/README.md +83 -83
  2. package/package.json +63 -59
  3. package/plugins/devin-auth/extensions/index.ts +23 -0
  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/oauth/register-user.ts +174 -174
  11. package/plugins/devin-auth/src/oauth/types.ts +71 -71
  12. package/plugins/devin-auth/src/stream.ts +1 -1
  13. package/plugins/pi-compact-tools/index.ts +19 -1
  14. package/plugins/pi-compact-tools/renderer.ts +231 -61
  15. package/plugins/pi-custom-agents/index.ts +310 -102
  16. package/plugins/pi-custom-agents/questionnaire-tool.ts +14 -5
  17. package/plugins/pi-custom-agents/subagent/extensions/agents.ts +18 -1
  18. package/plugins/pi-custom-agents/subagent/extensions/index.ts +116 -224
  19. package/plugins/pi-custom-agents/subagent/extensions/model.ts +96 -96
  20. package/plugins/pi-custom-agents/subagent/extensions/render.ts +205 -1
  21. package/plugins/pi-custom-agents/subagent/extensions/runner.ts +241 -177
  22. package/plugins/pi-custom-agents/subagent/extensions/test/render.test.ts +145 -0
  23. package/plugins/pi-ember-fff/index.ts +275 -17
  24. package/plugins/pi-ember-fff/query.ts +170 -10
  25. package/plugins/pi-ember-fff/test/query.test.ts +157 -1
  26. package/plugins/pi-ember-fff/test/renderer.test.ts +367 -16
  27. package/plugins/pi-ember-tps/index.ts +27 -5
  28. package/plugins/pi-ember-ui/ember.json +3 -0
  29. package/plugins/pi-ember-ui/index.ts +223 -36
  30. package/plugins/pi-ember-ui/mode-colors.ts +36 -8
@@ -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
+ }