@nredd/pi-subagents 0.22.0-nredd.6 → 0.22.0-nredd.8

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 (46) hide show
  1. package/README.md +160 -43
  2. package/package.json +8 -8
  3. package/src/agent-file-toggle.ts +6 -4
  4. package/src/agent-manager.ts +418 -118
  5. package/src/agent-runner.ts +155 -62
  6. package/src/cross-extension-rpc.ts +11 -36
  7. package/src/custom-agents.ts +9 -5
  8. package/src/default-agents.ts +2 -4
  9. package/src/enabled-models.ts +8 -7
  10. package/src/enterprise-local.ts +5 -5
  11. package/src/home-paths.ts +27 -0
  12. package/src/index.ts +231 -107
  13. package/src/invocation-config.ts +8 -35
  14. package/src/memory.ts +9 -9
  15. package/src/mention-clone.ts +9 -5
  16. package/src/model-resolver.ts +41 -11
  17. package/src/model-scope.ts +2 -1
  18. package/src/model-selection.ts +159 -0
  19. package/src/model-tiers.ts +171 -0
  20. package/src/nested-tools.ts +31 -26
  21. package/src/output-file.ts +23 -1
  22. package/src/package-resources.ts +25 -10
  23. package/src/process-budget.ts +346 -0
  24. package/src/quota-admission.ts +21 -9
  25. package/src/resource-loader.ts +51 -0
  26. package/src/schedule.ts +41 -19
  27. package/src/settings.ts +111 -8
  28. package/src/skill-loader.ts +8 -7
  29. package/src/stale-ctx.ts +16 -0
  30. package/src/types.ts +33 -0
  31. package/src/ui/agent-row.ts +13 -3
  32. package/src/ui/agent-widget.ts +15 -4
  33. package/src/ui/conversation-viewer.ts +6 -3
  34. package/src/ui/fallback-flag.ts +90 -0
  35. package/src/ui/fleet-list.ts +363 -53
  36. package/src/ui/fleet-state.ts +154 -0
  37. package/src/ui/notification-view.ts +18 -2
  38. package/src/ui/workflow-card.ts +5 -1
  39. package/src/ui/workflow-dialog.ts +10 -0
  40. package/src/workflow/host.ts +43 -45
  41. package/src/workflow/journal.ts +13 -1
  42. package/src/workflow/progress.ts +4 -0
  43. package/src/workflow/runtime.ts +25 -8
  44. package/src/workflow/saved.ts +19 -17
  45. package/src/workflow/task.ts +10 -0
  46. package/src/workflow/worker-source.ts +156 -38
@@ -14,6 +14,7 @@ import type { AgentRecord, ViewerMarkdownMode } from "../types.js";
14
14
  import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
15
15
  import type { Theme } from "./agent-widget.js";
16
16
  import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatCost, formatDuration, formatSessionTokens, getPromptModeLabel } from "./agent-widget.js";
17
+ import { fallbackFlag } from "./fallback-flag.js";
17
18
  import { createViewerKeys, type ViewerKeybindings, type ViewerKeys } from "./viewer-keys.js";
18
19
 
19
20
  /** Base lines consumed by chrome: top border + header + header sep + footer sep + footer + bottom border. */
@@ -727,9 +728,11 @@ export class ConversationViewer implements Component, Focusable {
727
728
  // models whose short names read alike.
728
729
  const { modelName, modelId, tags } = buildInvocationTags(this.record.invocation);
729
730
  const model = modelId ?? modelName;
730
- const parts = model ? [model, ...tags] : tags;
731
- if (parts.length === 0) return undefined;
732
- return this.theme.fg("dim", ` ↳ ${parts.join(" · ")}`);
731
+ const flag = fallbackFlag(this.theme, this.record);
732
+ const parts = [...(model ? [model] : []), ...tags];
733
+ if (parts.length === 0 && !flag) return undefined;
734
+ // The flag leads the (possibly very long) canonical model id.
735
+ return `${this.theme.fg("dim", " ↳")} ${flag ? `${flag} ` : ""}${this.theme.fg("dim", parts.join(" · "))}`;
733
736
  }
734
737
 
735
738
  private buildContentLines(width: number): string[] {
@@ -0,0 +1,90 @@
1
+ /**
2
+ * fallback-flag.ts -- the ONE compact indicator for "this agent ran on another
3
+ * model than configured", shared by every renderer that shows model info.
4
+ *
5
+ * Rows are `label + stats` clamped from the right, so anything appended after
6
+ * unbounded text (a description, an error, a canonical model id) is the first
7
+ * thing a narrow terminal cuts. The flag is therefore always rendered FIRST and
8
+ * the rest is truncated around it. Renderers must use these helpers rather than
9
+ * building their own marker; `test/fallback-flag-renderers.test.ts` walks every
10
+ * renderer and fails the ones that do not.
11
+ */
12
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
+ import type { TierDisclosure } from "../model-tiers.js";
14
+
15
+ /** Minimal theme surface, so this module has no dependency on the widgets that use it. */
16
+ interface FlagTheme {
17
+ fg(color: string, text: string): string;
18
+ }
19
+
20
+ /** Anything that can carry the fallback: a record, details or notice details. */
21
+ export interface FallbackCarrier {
22
+ modelFallback?: unknown;
23
+ modelNote?: string;
24
+ modelTier?: TierDisclosure;
25
+ }
26
+
27
+ /** Whether this tier's answer differs from the member/profile admitted for it. */
28
+ function switchedWithinTier(tier: TierDisclosure): boolean {
29
+ if (tier.kind !== "routed" && tier.kind !== "static") return false;
30
+ const baseline = tier.kind === "routed" ? tier.admitted : tier.member;
31
+ return baseline !== undefined && tier.answered !== undefined && tier.answered !== baseline;
32
+ }
33
+
34
+ /** Whether `carrier` needs an actionable fallback warning, not merely tier identity. */
35
+ export const hasFallback = (carrier: FallbackCarrier | undefined): boolean => {
36
+ if (carrier?.modelFallback !== undefined) return true;
37
+ const tier = carrier?.modelTier;
38
+ if (!tier) return carrier?.modelNote !== undefined;
39
+ return tier.kind === "unmapped" || tier.kind === "unavailable" || switchedWithinTier(tier);
40
+ };
41
+
42
+ /** Plain tier identity/outcome flag, or `[fallback]`; group counts retain the same identity. */
43
+ export function fallbackFlagText(count = 1, carrier?: FallbackCarrier): string {
44
+ const tier = carrier?.modelTier;
45
+ const suffix = count > 1 ? ` x${count}` : "";
46
+ return tier ? `[@${tier.tier}${tier.kind !== "routed" && tier.kind !== "static" ? ` ${tier.kind}` : ""}${suffix}]`
47
+ : `[fallback${suffix}]`;
48
+ }
49
+
50
+ /** The themed tier identity or actionable fallback flag for `carrier`. */
51
+ export function fallbackFlag(theme: FlagTheme, carrier: FallbackCarrier | undefined, count = 1): string {
52
+ if (carrier?.modelTier) return theme.fg(hasFallback(carrier) ? "warning" : "dim", fallbackFlagText(count, carrier));
53
+ return hasFallback(carrier) ? theme.fg("warning", fallbackFlagText(count, carrier)) : "";
54
+ }
55
+
56
+ /** What a flag degrades to when even it does not fit; plain so it cannot depend on a theme. */
57
+ const MINIMAL_FLAGS = ["[fb]", "!"] as const;
58
+
59
+ /**
60
+ * `prefix + flag + rest`, never wider than `width`, and the flag is never cut.
61
+ *
62
+ * Normal case: the prefix and the flag are reserved first and `rest` (a string,
63
+ * or a function of the width left for it) is truncated to what remains.
64
+ *
65
+ * Narrow case, when `prefix + flag + " "` exceeds `width`: the prefix and then
66
+ * the rest are dropped before the flag is. If the flag alone still does not
67
+ * fit, it degrades to a plain `[fb]`, then `!`. Only a zero width renders
68
+ * nothing.
69
+ */
70
+ export function withLeadingFlag(
71
+ prefix: string,
72
+ flag: string,
73
+ rest: string | ((inner: number) => string),
74
+ width: number,
75
+ ): string {
76
+ const lead = flag ? `${prefix}${flag} ` : prefix;
77
+ if (!flag || visibleWidth(lead) <= width) {
78
+ const inner = Math.max(0, width - visibleWidth(lead));
79
+ const body = typeof rest === "function" ? rest(inner) : truncateToWidth(rest, inner);
80
+ return truncateToWidth(lead + body, width);
81
+ }
82
+ const flagWidth = visibleWidth(flag);
83
+ if (flagWidth <= width) {
84
+ const inner = width - flagWidth - 1;
85
+ if (inner < 1) return flag;
86
+ const body = typeof rest === "function" ? rest(inner) : truncateToWidth(rest, inner);
87
+ return truncateToWidth(`${flag} ${body}`, width);
88
+ }
89
+ return MINIMAL_FLAGS.find(m => visibleWidth(m) <= width) ?? "";
90
+ }