@selesai/code 0.13.21 → 0.13.23

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 (27) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/extensions/capability-gateway/index.ts +28 -29
  3. package/dist/extensions/capability-gateway/integration.test.ts +43 -6
  4. package/dist/extensions/capability-gateway/package.json +1 -0
  5. package/dist/extensions/package.json +2 -0
  6. package/dist/extensions/pi-graft/cli.test.ts +408 -0
  7. package/dist/extensions/pi-graft/cli.ts +547 -0
  8. package/dist/extensions/pi-graft/commands.test.ts +478 -0
  9. package/dist/extensions/pi-graft/commands.ts +392 -0
  10. package/dist/extensions/pi-graft/graft.integration.test.ts +153 -0
  11. package/dist/extensions/pi-graft/index.test.ts +167 -0
  12. package/dist/extensions/pi-graft/index.ts +521 -0
  13. package/dist/extensions/pi-graft/lifecycle.test.ts +609 -0
  14. package/dist/extensions/pi-graft/prompt.test.ts +213 -0
  15. package/dist/extensions/pi-graft/prompt.ts +216 -0
  16. package/dist/extensions/pi-graft/state.test.ts +258 -0
  17. package/dist/extensions/pi-graft/state.ts +335 -0
  18. package/dist/extensions/pi-graft/test-support.ts +199 -0
  19. package/dist/extensions/pi-graft/tools.test.ts +330 -0
  20. package/dist/extensions/pi-graft/tools.ts +333 -0
  21. package/dist/extensions/pi-graft/tsconfig.json +16 -0
  22. package/dist/extensions/pi-subagents/src/agents/builtin-agent-augmentations.ts +71 -0
  23. package/dist/extensions/pi-subagents/src/extension/index.ts +9 -3
  24. package/dist/extensions/pi-subagents/test/unit/builtin-agent-augmentations.test.ts +44 -0
  25. package/dist/extensions/tps.test.ts +9 -1
  26. package/dist/extensions/tps.ts +9 -3
  27. package/package.json +2 -2
@@ -0,0 +1,71 @@
1
+ import type { ExtensionAPI } from "@selesai/code";
2
+ import type { AgentConfig } from "./agents.ts";
3
+
4
+ /** Public event emitted by pi-subagents while its parent extension is loading. */
5
+ export const BUILTIN_AGENT_AUGMENTATION_REQUEST_EVENT = "pi-subagents:request-builtin-agent-augmentations";
6
+
7
+ export interface BuiltinAgentAugmentation {
8
+ id: string;
9
+ agentNames: readonly string[];
10
+ addTools?: readonly string[];
11
+ subagentOnlyExtensions?: readonly string[];
12
+ }
13
+
14
+ interface BuiltinAgentAugmentationRequest {
15
+ register(augmentation: BuiltinAgentAugmentation): void;
16
+ }
17
+
18
+ function strings(value: unknown, field: string): string[] {
19
+ if (!Array.isArray(value) || value.length === 0 || value.some((entry) => typeof entry !== "string" || !entry.trim() || entry.trim() !== entry)) {
20
+ throw new Error(`Builtin agent augmentation ${field} must be a non-empty array of trimmed strings.`);
21
+ }
22
+ return [...new Set(value)];
23
+ }
24
+
25
+ function validate(augmentation: BuiltinAgentAugmentation): BuiltinAgentAugmentation {
26
+ if (!augmentation || typeof augmentation !== "object") throw new Error("Builtin agent augmentation must be an object.");
27
+ if (typeof augmentation.id !== "string" || !augmentation.id.trim() || augmentation.id.trim() !== augmentation.id) throw new Error("Builtin agent augmentation id must be a non-empty trimmed string.");
28
+ const agentNames = strings(augmentation.agentNames, "agentNames");
29
+ const addTools = augmentation.addTools === undefined ? undefined : strings(augmentation.addTools, "addTools");
30
+ const subagentOnlyExtensions = augmentation.subagentOnlyExtensions === undefined ? undefined : strings(augmentation.subagentOnlyExtensions, "subagentOnlyExtensions");
31
+ if (!addTools?.length && !subagentOnlyExtensions?.length) throw new Error("Builtin agent augmentation must add tools or child extensions.");
32
+ return { id: augmentation.id, agentNames, ...(addTools ? { addTools } : {}), ...(subagentOnlyExtensions ? { subagentOnlyExtensions } : {}) };
33
+ }
34
+
35
+ /** Ask already-loaded extensions for additive patches to pi-subagents builtins. */
36
+ export function requestBuiltinAgentAugmentations(pi: ExtensionAPI): BuiltinAgentAugmentation[] {
37
+ const byId = new Map<string, BuiltinAgentAugmentation>();
38
+ const request: BuiltinAgentAugmentationRequest = {
39
+ register(augmentation) {
40
+ const parsed = validate(augmentation);
41
+ if (byId.has(parsed.id)) throw new Error(`Builtin agent augmentation '${parsed.id}' was registered more than once.`);
42
+ byId.set(parsed.id, parsed);
43
+ },
44
+ };
45
+ pi.events.emit(BUILTIN_AGENT_AUGMENTATION_REQUEST_EVENT, request);
46
+ return [...byId.values()];
47
+ }
48
+
49
+ /**
50
+ * Apply patches only to selected builtins. User/project profiles and explicit
51
+ * builtin overrides retain precedence over an extension's defaults.
52
+ */
53
+ export function applyBuiltinAgentAugmentations(agents: AgentConfig[], augmentations: readonly BuiltinAgentAugmentation[]): AgentConfig[] {
54
+ return agents.map((agent) => {
55
+ if (agent.source !== "builtin") return agent;
56
+ const matches = augmentations.filter((augmentation) => augmentation.agentNames.includes(agent.name));
57
+ if (matches.length === 0) return agent;
58
+ const overridden = new Set(agent.override?.fields ?? []);
59
+ const tools = overridden.has("tools")
60
+ ? agent.tools
61
+ : [...new Set([...(agent.tools ?? []), ...matches.flatMap((augmentation) => augmentation.addTools ?? [])])];
62
+ const subagentOnlyExtensions = overridden.has("subagentOnlyExtensions")
63
+ ? agent.subagentOnlyExtensions
64
+ : [...new Set([...(agent.subagentOnlyExtensions ?? []), ...matches.flatMap((augmentation) => augmentation.subagentOnlyExtensions ?? [])])];
65
+ return {
66
+ ...agent,
67
+ ...(tools ? { tools } : {}),
68
+ ...(subagentOnlyExtensions ? { subagentOnlyExtensions } : {}),
69
+ };
70
+ });
71
+ }
@@ -20,6 +20,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
20
20
  import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@selesai/code";
21
21
  import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
22
22
  import { clearAgentDiscoveryCache, discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
23
+ import { applyBuiltinAgentAugmentations, requestBuiltinAgentAugmentations } from "../agents/builtin-agent-augmentations.ts";
23
24
  import { appendAdvertisedAgentPrompt, buildAdvertisedAgentPrompt } from "../agents/advertised-agent-prompt.ts";
24
25
  import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
25
26
  import { registerRuntimeAgentEventListener } from "../agents/runtime-agent-events.ts";
@@ -420,6 +421,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
420
421
  return;
421
422
  }
422
423
  const runtimeRegistry = getRuntimeRegistry();
424
+ const builtinAgentAugmentations = requestBuiltinAgentAugmentations(pi);
423
425
 
424
426
  DIRS.results = ensureAccessibleDir(DIRS.results);
425
427
  DIRS.async = ensureAccessibleDir(DIRS.async);
@@ -540,7 +542,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
540
542
  advertisedAgents = [];
541
543
  if (!advertisedContext) return;
542
544
  clearAgentDiscoveryCache();
543
- advertisedAgents = discoverAgents(advertisedContext.cwd, "both", advertisedContext.model?.provider).agents
545
+ advertisedAgents = discoverAgentsForRuntime(advertisedContext.cwd, "both", advertisedContext.model?.provider).agents
544
546
  .filter((agent) => agent.advertise === true);
545
547
  };
546
548
  const hasResultDeliveryDemand = () => {
@@ -550,9 +552,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
550
552
  return missionObserverResultCandidateFiles(DIRS.results).length > 0;
551
553
  };
552
554
  const discoverAgentsForRuntime = (cwd: string, scope: AgentScope, preferredModelProvider?: string) => {
553
- if (listRuntimeAgentConfigs(pi).length === 0) return discoverAgents(cwd, scope, preferredModelProvider);
555
+ const augment = <T extends ReturnType<typeof discoverAgents>>(discovered: T): T => ({
556
+ ...discovered,
557
+ agents: applyBuiltinAgentAugmentations(discovered.agents, builtinAgentAugmentations),
558
+ });
559
+ if (listRuntimeAgentConfigs(pi).length === 0) return augment(discoverAgents(cwd, scope, preferredModelProvider));
554
560
  const snapshot = discoverAgentSnapshot(cwd, scope, preferredModelProvider, { includeChains: false });
555
- const discovered = snapshot.effective;
561
+ const discovered = augment(snapshot.effective);
556
562
  const all = snapshot.all;
557
563
  const configuredAgents: AgentConfig[] = [
558
564
  ...all.builtin,
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { applyBuiltinAgentAugmentations, type BuiltinAgentAugmentation } from "../../src/agents/builtin-agent-augmentations.ts";
4
+ import type { AgentConfig } from "../../src/agents/agents.ts";
5
+
6
+ const graft: BuiltinAgentAugmentation = {
7
+ id: "pi-graft",
8
+ agentNames: ["worker"],
9
+ addTools: ["graft_find_code"],
10
+ subagentOnlyExtensions: ["/extensions/pi-graft/index.ts"],
11
+ };
12
+
13
+ function agent(source: AgentConfig["source"], fields: Partial<AgentConfig> = {}): AgentConfig {
14
+ return {
15
+ name: "worker",
16
+ description: "Worker",
17
+ systemPrompt: "Work.",
18
+ systemPromptMode: "replace",
19
+ inheritProjectContext: false,
20
+ inheritGlobalContext: false,
21
+ inheritSkills: false,
22
+ filePath: "/agents/worker.md",
23
+ source,
24
+ tools: ["read"],
25
+ ...fields,
26
+ };
27
+ }
28
+
29
+ describe("builtin agent augmentations", () => {
30
+ it("augments only an uncustomized selected builtin", () => {
31
+ const [builtin, user, overridden] = applyBuiltinAgentAugmentations([
32
+ agent("builtin"),
33
+ agent("user"),
34
+ agent("builtin", { override: { scope: "user", path: "/settings.json", fields: ["tools", "subagentOnlyExtensions"] } }),
35
+ ], [graft]);
36
+
37
+ assert.deepEqual(builtin?.tools, ["read", "graft_find_code"]);
38
+ assert.deepEqual(builtin?.subagentOnlyExtensions, ["/extensions/pi-graft/index.ts"]);
39
+ assert.deepEqual(user?.tools, ["read"]);
40
+ assert.equal(user?.subagentOnlyExtensions, undefined);
41
+ assert.deepEqual(overridden?.tools, ["read"]);
42
+ assert.equal(overridden?.subagentOnlyExtensions, undefined);
43
+ });
44
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { calculateReliableTps, type TpsTiming } from "./tps.ts";
2
+ import { calculateLiveTps, calculateReliableTps, type TpsTiming } from "./tps.ts";
3
3
 
4
4
  function timing(overrides: Partial<TpsTiming> = {}): TpsTiming {
5
5
  return {
@@ -15,6 +15,14 @@ function timing(overrides: Partial<TpsTiming> = {}): TpsTiming {
15
15
  };
16
16
  }
17
17
 
18
+ describe("calculateLiveTps", () => {
19
+ it("waits for a usable sample and rejects implausible rates", () => {
20
+ expect(calculateLiveTps(60, 0.2)).toBeNull();
21
+ expect(calculateLiveTps(100, 200)).toBe(500);
22
+ expect(calculateLiveTps(10_001, 1_000)).toBeNull();
23
+ });
24
+ });
25
+
18
26
  describe("calculateReliableTps", () => {
19
27
  it("uses active stream time for sufficiently sampled output", () => {
20
28
  expect(calculateReliableTps(100, timing())).toEqual({
@@ -75,6 +75,12 @@ export function calculateReliableTps(outputTokens: number, timing: TpsTiming): R
75
75
  return { tps, effectiveMs, isPrimary };
76
76
  }
77
77
 
78
+ export function calculateLiveTps(tokens: number, elapsedMs: number): number | null {
79
+ if (tokens <= 0 || elapsedMs < MIN_GENERATION_MS) return null;
80
+ const tps = Math.round(tokens / (elapsedMs / 1000));
81
+ return tps <= MAX_PLAUSIBLE_TPS ? tps : null;
82
+ }
83
+
78
84
  export default function tpsExtension(pi: ExtensionAPI): void {
79
85
  setupTpsTracker(pi);
80
86
  }
@@ -223,11 +229,11 @@ export function setupTpsTracker(pi: ExtensionAPI): void {
223
229
 
224
230
  streamStart ??= now;
225
231
  estimatedStreamedTokens += Math.max(0, streamEvent.delta.length / 4);
226
- const elapsed = (now - streamStart) / 1000;
227
232
  const officialTokens = generatedTokensFromUsage(asRecord(event.message.usage));
228
233
  const currentTokens = officialTokens > 0 ? officialTokens : estimatedStreamedTokens;
229
- if (elapsed > 0 && currentTokens > 0) {
230
- ctx.ui.setStatus("tps", ctx.ui.theme.fg("accent", `${Math.round(currentTokens / elapsed)} tok/s`));
234
+ const tps = calculateLiveTps(currentTokens, now - streamStart);
235
+ if (tps !== null) {
236
+ ctx.ui.setStatus("tps", ctx.ui.theme.fg("accent", `${tps} tok/s`));
231
237
  }
232
238
  });
233
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.13.21",
3
+ "version": "0.13.23",
4
4
  "description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -52,7 +52,7 @@
52
52
  "clean": "shx rm -rf dist",
53
53
  "dev": "tsx src/cli.ts",
54
54
  "dev:print": "tsx src/cli.ts --print",
55
- "test": "vitest run src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/cli/args.test.ts",
55
+ "test": "vitest run src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/pi-graft src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/cli/args.test.ts",
56
56
  "test:coverage": "vitest run --coverage src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/cli/args.test.ts",
57
57
  "prepare": "npm run build",
58
58
  "build": "npm run clean && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",