@oh-my-pi/pi-coding-agent 14.8.1 → 14.9.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 (56) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/package.json +16 -7
  3. package/src/config/model-resolver.ts +92 -35
  4. package/src/config/prompt-templates.ts +1 -1
  5. package/src/debug/index.ts +21 -0
  6. package/src/debug/raw-sse-buffer.ts +229 -0
  7. package/src/debug/raw-sse.ts +213 -0
  8. package/src/edit/index.ts +9 -10
  9. package/src/edit/streaming.ts +6 -5
  10. package/src/eval/js/context-manager.ts +91 -47
  11. package/src/extensibility/extensions/loader.ts +9 -3
  12. package/src/extensibility/plugins/legacy-pi-compat.ts +99 -20
  13. package/src/hashline/anchors.ts +113 -0
  14. package/src/hashline/apply.ts +732 -0
  15. package/src/hashline/bigrams.json +649 -0
  16. package/src/hashline/constants.ts +8 -0
  17. package/src/hashline/diff-preview.ts +43 -0
  18. package/src/hashline/diff.ts +56 -0
  19. package/src/hashline/execute.ts +268 -0
  20. package/src/{edit/modes/hashline.lark → hashline/grammar.lark} +1 -1
  21. package/src/{edit/line-hash.ts → hashline/hash.ts} +5 -651
  22. package/src/hashline/index.ts +14 -0
  23. package/src/hashline/input.ts +110 -0
  24. package/src/hashline/parser.ts +220 -0
  25. package/src/hashline/prefixes.ts +101 -0
  26. package/src/hashline/recovery.ts +72 -0
  27. package/src/hashline/stream.ts +123 -0
  28. package/src/hashline/types.ts +69 -0
  29. package/src/hashline/utils.ts +3 -0
  30. package/src/index.ts +1 -1
  31. package/src/lsp/index.ts +1 -1
  32. package/src/lsp/render.ts +4 -0
  33. package/src/memories/index.ts +13 -4
  34. package/src/modes/components/assistant-message.ts +55 -9
  35. package/src/modes/components/welcome.ts +114 -38
  36. package/src/modes/controllers/event-controller.ts +3 -1
  37. package/src/modes/controllers/input-controller.ts +8 -1
  38. package/src/modes/interactive-mode.ts +9 -9
  39. package/src/modes/rpc/rpc-client.ts +53 -2
  40. package/src/modes/rpc/rpc-mode.ts +67 -1
  41. package/src/modes/rpc/rpc-types.ts +17 -2
  42. package/src/modes/utils/ui-helpers.ts +3 -1
  43. package/src/prompts/agents/reviewer.md +14 -0
  44. package/src/prompts/tools/hashline.md +57 -10
  45. package/src/sdk.ts +4 -3
  46. package/src/session/agent-session.ts +195 -30
  47. package/src/session/compaction/branch-summarization.ts +4 -2
  48. package/src/session/compaction/compaction.ts +22 -3
  49. package/src/task/executor.ts +21 -2
  50. package/src/task/index.ts +4 -1
  51. package/src/tools/ast-edit.ts +1 -1
  52. package/src/tools/match-line-format.ts +1 -1
  53. package/src/tools/read.ts +1 -1
  54. package/src/utils/file-mentions.ts +1 -1
  55. package/src/utils/title-generator.ts +11 -0
  56. package/src/edit/modes/hashline.ts +0 -2039
@@ -965,6 +965,7 @@ export interface SummaryOptions {
965
965
  remoteEndpoint?: string;
966
966
  remoteInstructions?: string;
967
967
  initiatorOverride?: MessageAttribution;
968
+ metadata?: Record<string, unknown>;
968
969
  }
969
970
 
970
971
  export async function generateSummary(
@@ -1020,7 +1021,14 @@ export async function generateSummary(
1020
1021
  const response = await completeSimple(
1021
1022
  model,
1022
1023
  { systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
1023
- { maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
1024
+ {
1025
+ maxTokens,
1026
+ signal,
1027
+ apiKey,
1028
+ reasoning: Effort.High,
1029
+ initiatorOverride: options?.initiatorOverride,
1030
+ metadata: options?.metadata,
1031
+ },
1024
1032
  );
1025
1033
 
1026
1034
  if (response.stopReason === "error") {
@@ -1069,7 +1077,14 @@ async function generateShortSummary(
1069
1077
  systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT],
1070
1078
  messages: [{ role: "user", content: [{ type: "text", text: promptText }], timestamp: Date.now() }],
1071
1079
  },
1072
- { maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
1080
+ {
1081
+ maxTokens,
1082
+ signal,
1083
+ apiKey,
1084
+ reasoning: Effort.High,
1085
+ initiatorOverride: options?.initiatorOverride,
1086
+ metadata: options?.metadata,
1087
+ },
1073
1088
  );
1074
1089
 
1075
1090
  if (response.stopReason === "error") {
@@ -1249,6 +1264,7 @@ export async function compact(
1249
1264
  remoteEndpoint: settings.remoteEnabled === false ? undefined : settings.remoteEndpoint,
1250
1265
  remoteInstructions: options?.remoteInstructions,
1251
1266
  initiatorOverride: options?.initiatorOverride,
1267
+ metadata: options?.metadata,
1252
1268
  };
1253
1269
 
1254
1270
  let preserveData = withOpenAiRemoteCompactionPreserveData(previousPreserveData, undefined);
@@ -1304,6 +1320,7 @@ export async function compact(
1304
1320
  apiKey,
1305
1321
  signal,
1306
1322
  summaryOptions.initiatorOverride,
1323
+ summaryOptions.metadata,
1307
1324
  ),
1308
1325
  ]);
1309
1326
  // Merge into single summary
@@ -1339,6 +1356,7 @@ export async function compact(
1339
1356
  extraContext: options?.extraContext,
1340
1357
  remoteEndpoint: summaryOptions.remoteEndpoint,
1341
1358
  initiatorOverride: summaryOptions.initiatorOverride,
1359
+ metadata: summaryOptions.metadata,
1342
1360
  },
1343
1361
  );
1344
1362
 
@@ -1370,6 +1388,7 @@ async function generateTurnPrefixSummary(
1370
1388
  apiKey: string,
1371
1389
  signal?: AbortSignal,
1372
1390
  initiatorOverride?: MessageAttribution,
1391
+ metadata?: Record<string, unknown>,
1373
1392
  ): Promise<string> {
1374
1393
  const maxTokens = Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix
1375
1394
 
@@ -1387,7 +1406,7 @@ async function generateTurnPrefixSummary(
1387
1406
  const response = await completeSimple(
1388
1407
  model,
1389
1408
  { systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
1390
- { maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride },
1409
+ { maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride, metadata },
1391
1410
  );
1392
1411
 
1393
1412
  if (response.stopReason === "error") {
@@ -10,7 +10,7 @@ import { logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
10
10
  import type { TSchema } from "@sinclair/typebox";
11
11
  import Ajv, { type ValidateFunction } from "ajv";
12
12
  import { ModelRegistry } from "../config/model-registry";
13
- import { resolveModelOverride } from "../config/model-resolver";
13
+ import { resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
14
14
  import type { PromptTemplate } from "../config/prompt-templates";
15
15
  import { Settings } from "../config/settings";
16
16
  import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
@@ -144,6 +144,11 @@ export interface ExecutorOptions {
144
144
  index: number;
145
145
  id: string;
146
146
  modelOverride?: string | string[];
147
+ /**
148
+ * Active model selector of the parent session, used as an auth-aware fallback
149
+ * if the resolved subagent model has no working credentials. See #985.
150
+ */
151
+ parentActiveModelPattern?: string;
147
152
  thinkingLevel?: ThinkingLevel;
148
153
  outputSchema?: unknown;
149
154
  /** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
@@ -944,7 +949,21 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
944
949
  model,
945
950
  thinkingLevel: resolvedThinkingLevel,
946
951
  explicitThinkingLevel,
947
- } = resolveModelOverride(modelPatterns, modelRegistry, settings);
952
+ authFallbackUsed,
953
+ } = await resolveModelOverrideWithAuthFallback(
954
+ modelPatterns,
955
+ options.parentActiveModelPattern,
956
+ modelRegistry,
957
+ settings,
958
+ );
959
+ if (authFallbackUsed && model) {
960
+ logger.warn("Subagent model has no working credentials; falling back to parent session model", {
961
+ requested: modelPatterns,
962
+ parentModel: options.parentActiveModelPattern,
963
+ resolvedProvider: model.provider,
964
+ resolvedModel: model.id,
965
+ });
966
+ }
948
967
  const effectiveThinkingLevel = explicitThinkingLevel
949
968
  ? resolvedThinkingLevel
950
969
  : (thinkingLevel ?? resolvedThinkingLevel);
package/src/task/index.ts CHANGED
@@ -583,11 +583,12 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
583
583
  // Apply per-agent model override from settings (highest priority)
584
584
  const agentModelOverrides = this.session.settings.get("task.agentModelOverrides");
585
585
  const settingsModelOverride = agentModelOverrides[agentName];
586
+ const parentActiveModelPattern = this.session.getActiveModelString?.();
586
587
  const modelOverride = resolveAgentModelPatterns({
587
588
  settingsOverride: settingsModelOverride,
588
589
  agentModel: effectiveAgent.model,
589
590
  settings: this.session.settings,
590
- activeModelPattern: this.session.getActiveModelString?.(),
591
+ activeModelPattern: parentActiveModelPattern,
591
592
  fallbackModelPattern: this.session.getModelString?.(),
592
593
  });
593
594
  const thinkingLevelOverride = effectiveAgent.thinkingLevel;
@@ -843,6 +844,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
843
844
  id: task.id,
844
845
  taskDepth,
845
846
  modelOverride,
847
+ parentActiveModelPattern,
846
848
  thinkingLevel: thinkingLevelOverride,
847
849
  outputSchema: effectiveOutputSchema,
848
850
  sessionFile,
@@ -900,6 +902,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
900
902
  id: task.id,
901
903
  taskDepth,
902
904
  modelOverride,
905
+ parentActiveModelPattern,
903
906
  thinkingLevel: thinkingLevelOverride,
904
907
  outputSchema: effectiveOutputSchema,
905
908
  sessionFile,
@@ -5,8 +5,8 @@ import type { Component } from "@oh-my-pi/pi-tui";
5
5
  import { Text } from "@oh-my-pi/pi-tui";
6
6
  import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
7
7
  import { type Static, Type } from "@sinclair/typebox";
8
- import { computeLineHash, HL_BODY_SEP } from "../edit/line-hash";
9
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
+ import { computeLineHash, HL_BODY_SEP } from "../hashline/hash";
10
10
  import type { Theme } from "../modes/theme/theme";
11
11
  import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text" };
12
12
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
@@ -1,4 +1,4 @@
1
- import { computeLineHash } from "../edit/line-hash";
1
+ import { computeLineHash } from "../hashline/hash";
2
2
 
3
3
  /**
4
4
  * Format a single line of match output for grep/ast-grep style results.
package/src/tools/read.ts CHANGED
@@ -9,9 +9,9 @@ import { Text } from "@oh-my-pi/pi-tui";
9
9
  import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
10
10
  import { type Static, Type } from "@sinclair/typebox";
11
11
  import { getFileReadCache } from "../edit/file-read-cache";
12
- import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../edit/line-hash";
13
12
  import { isNotebookPath, readEditableNotebookText } from "../edit/notebook";
14
13
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
+ import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../hashline/hash";
15
15
  import { parseInternalUrl } from "../internal-urls/parse";
16
16
  import type { InternalUrl } from "../internal-urls/types";
17
17
  import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
@@ -11,7 +11,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
11
11
  import type { ImageContent } from "@oh-my-pi/pi-ai";
12
12
  import { glob } from "@oh-my-pi/pi-natives";
13
13
  import { formatAge, formatBytes, readImageMetadata } from "@oh-my-pi/pi-utils";
14
- import { formatHashLines } from "../edit/line-hash";
14
+ import { formatHashLines } from "../hashline/hash";
15
15
  import type { FileMentionMessage } from "../session/messages";
16
16
  import {
17
17
  DEFAULT_MAX_BYTES,
@@ -36,6 +36,11 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
36
36
  * @param registry Model registry
37
37
  * @param settings Settings used to resolve the smol role
38
38
  * @param sessionId Optional session id for sticky API key selection
39
+ * @param currentModel Current model (used to derive title model)
40
+ * @param metadataResolver Optional resolver evaluated after credential selection
41
+ * to produce request metadata (e.g. user_id for session attribution). Using a
42
+ * resolver instead of a pre-evaluated value ensures the metadata's account_uuid
43
+ * reflects the credential actually selected for this request.
39
44
  */
40
45
  export async function generateSessionTitle(
41
46
  firstMessage: string,
@@ -43,6 +48,7 @@ export async function generateSessionTitle(
43
48
  settings: Settings,
44
49
  sessionId?: string,
45
50
  currentModel?: Model<Api>,
51
+ metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
46
52
  ): Promise<string | null> {
47
53
  const model = getTitleModel(registry, settings, currentModel);
48
54
  if (!model) {
@@ -65,6 +71,10 @@ ${truncatedMessage}
65
71
  });
66
72
  return null;
67
73
  }
74
+ // Resolve metadata after getApiKey so the session-sticky credential for this
75
+ // request is already recorded; metadataResolver can then return the correct
76
+ // account_uuid rather than the snapshot-at-call-site value.
77
+ const metadata = metadataResolver?.(model.provider);
68
78
 
69
79
  // Title generation is a 3-6 word task; force reasoning off so reasoning models
70
80
  // don't burn the entire output budget on internal thinking and return an empty
@@ -88,6 +98,7 @@ ${truncatedMessage}
88
98
  apiKey,
89
99
  maxTokens: 30,
90
100
  disableReasoning: true,
101
+ metadata,
91
102
  },
92
103
  );
93
104