@oh-my-pi/pi-coding-agent 17.2.11 → 17.2.12

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 (152) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/{CHANGELOG-d9xenpn9.md → CHANGELOG-k9ghy5sn.md} +13 -0
  3. package/dist/cli.js +10795 -10791
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/config/custom-models.d.ts +31 -0
  6. package/dist/types/config/model-config-values.d.ts +19 -0
  7. package/dist/types/config/model-patch.d.ts +93 -0
  8. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  9. package/dist/types/config/model-registry.d.ts +5 -52
  10. package/dist/types/config/settings.d.ts +5 -3
  11. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  12. package/dist/types/discovery/agent-plugin-format.d.ts +6 -13
  13. package/dist/types/discovery/contained-path.d.ts +2 -3
  14. package/dist/types/eval/executor-base.d.ts +8 -2
  15. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  16. package/dist/types/export/share.d.ts +1 -1
  17. package/dist/types/lsp/diagnostics.d.ts +96 -0
  18. package/dist/types/lsp/index.d.ts +7 -128
  19. package/dist/types/lsp/servers.d.ts +72 -0
  20. package/dist/types/lsp/tool.d.ts +42 -0
  21. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  22. package/dist/types/lsp/writethrough.d.ts +33 -0
  23. package/dist/types/mcp/transports/header-policy.d.ts +2 -1
  24. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  25. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  26. package/dist/types/modes/theme/color.d.ts +19 -0
  27. package/dist/types/modes/theme/loader.d.ts +19 -0
  28. package/dist/types/modes/theme/schema.d.ts +175 -0
  29. package/dist/types/modes/theme/symbols.d.ts +28 -0
  30. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  31. package/dist/types/modes/theme/theme.d.ts +9 -280
  32. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  33. package/dist/types/registry/agent-registry.d.ts +1 -3
  34. package/dist/types/secrets/index.d.ts +3 -1
  35. package/dist/types/secrets/message-transform.d.ts +40 -0
  36. package/dist/types/secrets/obfuscator.d.ts +0 -108
  37. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  38. package/dist/types/secrets/placeholder.d.ts +94 -0
  39. package/dist/types/secrets/replacement.d.ts +82 -0
  40. package/dist/types/session/agent-session-types.d.ts +6 -0
  41. package/dist/types/session/agent-session.d.ts +1 -1
  42. package/dist/types/session/session-handoff.d.ts +3 -3
  43. package/dist/types/session/session-manager.d.ts +32 -0
  44. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  45. package/dist/types/session/turn-recovery.d.ts +2 -2
  46. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  47. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  48. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  49. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  50. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  51. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  52. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  53. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  54. package/dist/types/tools/gh-common.d.ts +69 -0
  55. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  56. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  57. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  58. package/dist/types/tools/gh-search.d.ts +61 -0
  59. package/dist/types/tools/gh-types.d.ts +340 -0
  60. package/dist/types/tools/gh-view.d.ts +88 -0
  61. package/dist/types/tools/gh.d.ts +4 -201
  62. package/dist/types/tools/read-archive.d.ts +16 -0
  63. package/dist/types/tools/read-format.d.ts +104 -0
  64. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  65. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  66. package/dist/types/tools/read-renderer.d.ts +24 -0
  67. package/dist/types/tools/read-selector.d.ts +27 -0
  68. package/dist/types/tools/read-sqlite.d.ts +16 -0
  69. package/dist/types/tools/read-summary.d.ts +19 -0
  70. package/dist/types/tools/read.d.ts +1 -23
  71. package/dist/types/tools/shell-tokenize.d.ts +2 -1
  72. package/dist/types/utils/changelog.d.ts +0 -5
  73. package/package.json +13 -13
  74. package/src/advisor/runtime.ts +12 -7
  75. package/src/config/custom-models.ts +188 -0
  76. package/src/config/model-config-values.ts +128 -0
  77. package/src/config/model-patch.ts +252 -0
  78. package/src/config/model-provider-discovery.ts +132 -0
  79. package/src/config/model-registry.ts +64 -704
  80. package/src/config/settings.ts +7 -3
  81. package/src/debug/raw-sse-buffer.ts +20 -0
  82. package/src/discovery/agent-plugin-format.ts +7 -7
  83. package/src/discovery/contained-path.ts +2 -5
  84. package/src/edit/hashline/diff.ts +5 -1
  85. package/src/eval/executor-base.ts +39 -6
  86. package/src/eval/jl/executor.ts +82 -371
  87. package/src/eval/kernel-session-registry.ts +398 -0
  88. package/src/eval/py/executor.ts +53 -261
  89. package/src/eval/rb/executor.ts +36 -279
  90. package/src/export/share.ts +2 -1
  91. package/src/lsp/diagnostics.ts +516 -0
  92. package/src/lsp/index.ts +20 -2819
  93. package/src/lsp/servers.ts +296 -0
  94. package/src/lsp/tool.ts +1352 -0
  95. package/src/lsp/workspace-diagnostics.ts +170 -0
  96. package/src/lsp/writethrough.ts +561 -0
  97. package/src/mcp/transports/header-policy.ts +1 -1
  98. package/src/modes/components/agent-hub-projection.ts +2 -2
  99. package/src/modes/components/agent-hub-renderer.ts +3 -7
  100. package/src/modes/controllers/command-controller.ts +4 -1
  101. package/src/modes/theme/color.ts +133 -0
  102. package/src/modes/theme/loader.ts +178 -0
  103. package/src/modes/theme/schema.ts +263 -0
  104. package/src/modes/theme/symbols.ts +1000 -0
  105. package/src/modes/theme/theme-class.ts +611 -0
  106. package/src/modes/theme/theme.ts +27 -2453
  107. package/src/modes/theme/tui-adapters.ts +279 -0
  108. package/src/registry/agent-registry.ts +2 -2
  109. package/src/secrets/index.ts +6 -12
  110. package/src/secrets/message-transform.ts +287 -0
  111. package/src/secrets/obfuscator.ts +39 -1303
  112. package/src/secrets/placeholder-scan.ts +506 -0
  113. package/src/secrets/placeholder.ts +309 -0
  114. package/src/secrets/replacement.ts +216 -0
  115. package/src/session/agent-session-types.ts +6 -0
  116. package/src/session/agent-session.ts +113 -7
  117. package/src/session/indexed-session-storage.ts +7 -2
  118. package/src/session/session-advisors.ts +32 -34
  119. package/src/session/session-handoff.ts +39 -20
  120. package/src/session/session-manager.ts +67 -3
  121. package/src/session/session-provider-boundary.ts +3 -6
  122. package/src/session/turn-recovery.ts +21 -12
  123. package/src/slash-commands/builtin-collaboration.ts +504 -0
  124. package/src/slash-commands/builtin-completions.ts +291 -0
  125. package/src/slash-commands/builtin-control.ts +78 -0
  126. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  127. package/src/slash-commands/builtin-marketplace.ts +567 -0
  128. package/src/slash-commands/builtin-modes.ts +518 -0
  129. package/src/slash-commands/builtin-registry.ts +21 -3000
  130. package/src/slash-commands/builtin-session.ts +592 -0
  131. package/src/task/executor.ts +17 -14
  132. package/src/tools/gh-common.ts +288 -0
  133. package/src/tools/gh-pr-checkout.ts +679 -0
  134. package/src/tools/gh-pr-diff.ts +473 -0
  135. package/src/tools/gh-run-watch.ts +1015 -0
  136. package/src/tools/gh-search.ts +500 -0
  137. package/src/tools/gh-types.ts +379 -0
  138. package/src/tools/gh-view.ts +612 -0
  139. package/src/tools/gh.ts +109 -3821
  140. package/src/tools/read-archive.ts +209 -0
  141. package/src/tools/read-format.ts +593 -0
  142. package/src/tools/read-path-resolution.ts +36 -0
  143. package/src/tools/read-pdf-images.ts +250 -0
  144. package/src/tools/read-renderer.ts +289 -0
  145. package/src/tools/read-selector.ts +84 -0
  146. package/src/tools/read-sqlite.ts +215 -0
  147. package/src/tools/read-summary.ts +199 -0
  148. package/src/tools/read.ts +77 -1820
  149. package/src/tools/shell-tokenize.ts +1 -1
  150. package/src/utils/changelog.ts +1 -1
  151. package/src/utils/title-generator.ts +3 -1
  152. package/src/web/search/providers/zai.ts +12 -3
@@ -1,6 +1,3 @@
1
- import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
- import type { AssistantMessage, Context, Message } from "@oh-my-pi/pi-ai";
3
- import type { SessionContext } from "../session/session-context.js";
4
1
  export interface SecretEntry {
5
2
  type: "plain" | "regex";
6
3
  content: string;
@@ -15,75 +12,6 @@ export type JsonValue = string | number | boolean | null | JsonValue[] | {
15
12
  export type JsonRecord = {
16
13
  [key: string]: JsonValue | undefined;
17
14
  };
18
- /**
19
- * Whether a default (no custom `replacement`) replace-mode regex can never
20
- * safely redact a 1-2 char match: `findNonMatchingReplacement`'s bounded
21
- * search — the same search `#generateRegexReplacement` runs at match time —
22
- * finds no candidate the regex fails to re-match. This holds independent of
23
- * any actual per-install key: the search already exhausts every character in
24
- * `REPLACEMENT_CHARS` (the alphabet `buildKeyedReplacementRun` draws its
25
- * fallback marker from) plus punctuation and whitespace, so if none of those
26
- * escape the regex, no key-derived marker drawn from the same alphabet can
27
- * either — the marker is guaranteed to re-match too, making every such match
28
- * unresolvable: the fallback could only ever emit the raw matched text
29
- * unchanged. Probed with a value (`"\0".repeat(length)`) the bounded search
30
- * never treats as a real candidate, so the result depends only on the
31
- * regex's own matching behavior, not on this specific probe.
32
- */
33
- export declare function regexHasUnresolvableShortMatchFallback(regex: RegExp): boolean;
34
- export declare const MIN_OBFUSCATE_SECRET_LEN = 8;
35
- /** Normalize a friendly name into the model-visible placeholder prefix. */
36
- export declare function sanitizeSecretFriendlyName(name: string): string | undefined;
37
- /**
38
- * Whether an entry needs the persisted placeholder key: either because it can
39
- * produce a reversible (keyed) obfuscate-mode placeholder, or because a default
40
- * (no custom `replacement`) replace-mode regex can reach
41
- * `#generateRegexReplacement`'s key-derived idempotent fallback marker (see
42
- * `#generateReplacement`) when every same-length candidate re-matches a
43
- * pathological match-everything config (e.g. `[\s\S]{8}`). That fallback depends
44
- * on the persisted per-install key — not just length — to stay a fixed point
45
- * across a process restart; without a persisted key, a fresh install falls back
46
- * to a process-random key (`defaultPlaceholderKey()`), so the fallback marker
47
- * would churn across restarts even though the algorithm itself is stable. A
48
- * regex WITH a custom `replacement` never reaches that fallback (it always emits
49
- * the literal configured string), and a plain replace secret's replacement is
50
- * pure content-hash (`#generateSecretReplacement`), so neither needs the key.
51
- * Short plain obfuscate entries are toned down (never placeheld), so they must
52
- * NOT force key creation: otherwise a `secret-placeholder.key` file is written
53
- * and persisted for a config that ends up with no active secrets, leaving the
54
- * key readable via a tool and reusable for later placeholders.
55
- */
56
- export declare function secretEntryNeedsPlaceholderKey(entry: SecretEntry): boolean;
57
- /**
58
- * Whether a SET of entries needs the persisted placeholder key. `obfuscate()`
59
- * applies plain replace-mode mappings before the plain-obfuscate pass, so a plain
60
- * obfuscate entry only emits a reversible (keyed) placeholder when its content can
61
- * still appear AFTER the replace phase. When no obfuscate entry can ever produce a
62
- * placeholder, the persisted key must NOT be required/created — otherwise an
63
- * effectively replace-only secret set still writes `secret-placeholder.key` and
64
- * fails startup when the agent config dir is unwritable.
65
- *
66
- * The decision models the replace phase as the obfuscator actually runs it:
67
- * replace mappings are content-keyed (later duplicate wins) and applied in
68
- * descending content-length order; for a fresh probe (no prior placeholders) that
69
- * phase is plain sequential substring replacement. A plain obfuscate entry needs
70
- * the key when its content survives that simulated phase (direct typing) OR when
71
- * any effective replacement can form the content via tiling — a substring,
72
- * wholesale superstring, or prefix/suffix border that joins with surrounding
73
- * passthrough bytes (see `replacementCanFormContent`). This covers direct
74
- * shadowing (`SECRET -> safe`), reintroduction, duplicate ordering, transitive
75
- * chains, and context-joined fragments uniformly. Default (omitted) replacements
76
- * are deterministic, length-preserving, and distinct, so a same-content shadow
77
- * with no other interacting replacement stays key-free.
78
- * Replacement outputs are themselves rewritten by every later (shorter-content)
79
- * replacement before the plain-obfuscate pass sees them, so a fragment that a
80
- * subsequent replacement erases (`AA -> SEC` then `S -> X` turns every `SEC` into
81
- * `XEC`) no longer forces the key. Surrounding bytes stay modeled as arbitrary
82
- * passthrough, so testing the surviving fragment only drops false positives and
83
- * never under-approximates a real key need.
84
- */
85
- export declare function secretEntriesNeedPlaceholderKey(entries: SecretEntry[]): boolean;
86
- export declare function stripPendingSecretPlaceholderSuffix(text: string): string;
87
15
  export declare class SecretObfuscator {
88
16
  #private;
89
17
  constructor(entries: SecretEntry[], key?: string | (() => string));
@@ -99,39 +27,3 @@ export declare class SecretObfuscator {
99
27
  collectRegexSecretValuesForObfuscation(text: string): Set<string>;
100
28
  stripUnsafeFriendlyPlaceholderPrefixes(text: string, sharedRegexSecretValues: ReadonlySet<string>): string;
101
29
  }
102
- /**
103
- * Restore secret placeholders for local display. Only message kinds the model
104
- * itself authored from obfuscated context carry placeholders — assistant
105
- * content and the LLM-written branch/compaction summaries. User, developer, and
106
- * tool-result messages are persisted with their literal text, so operator-authored
107
- * placeholder-shaped text must survive untouched; those roles are never walked.
108
- */
109
- export declare function deobfuscateSessionContext(sessionContext: SessionContext, obfuscator: SecretObfuscator | undefined): SessionContext;
110
- export declare function deobfuscateAgentMessages(obfuscator: SecretObfuscator, messages: AgentMessage[]): AgentMessage[];
111
- /**
112
- * Restore placeholders in assistant content: visible text and tool-call
113
- * arguments/intent/rawBlock. Thinking and signatures are opaque
114
- * provider-replay/hidden-reasoning data and pass through byte-identical.
115
- */
116
- export declare function deobfuscateAssistantContent(obfuscator: SecretObfuscator, content: AssistantMessage["content"]): AssistantMessage["content"];
117
- /**
118
- * Restore placeholders inside a tool call's arguments. Arguments are arbitrary
119
- * model-authored JSON, so tool-call arguments are the ONLY place a recursive
120
- * JSON walk runs.
121
- */
122
- export declare function deobfuscateToolArguments(obfuscator: SecretObfuscator, args: Record<string, unknown>): Record<string, unknown>;
123
- /** Redact secrets inside a tool call's arguments (same JSON-walk exception as {@link deobfuscateToolArguments}). */
124
- export declare function obfuscateToolArguments(obfuscator: SecretObfuscator, args: Record<string, unknown>, sharedRegexSecretValues?: ReadonlySet<string>): Record<string, unknown>;
125
- /**
126
- * Redact secrets from outbound messages. User messages, tool results, and
127
- * user-authored developer messages (e.g. `@file` mentions) are obfuscated.
128
- * Assistant replay content is re-obfuscated too, because session restoration
129
- * expands keyed placeholders locally before the next provider request. Inline
130
- * image bytes are never walked.
131
- */
132
- export declare function obfuscateMessages(obfuscator: SecretObfuscator, messages: Message[]): Message[];
133
- /**
134
- * Redact outbound provider context. Only conversation messages are rewritten;
135
- * the static system prompt and tool schemas pass through unchanged.
136
- */
137
- export declare function obfuscateProviderContext(obfuscator: SecretObfuscator | undefined, context: Context): Context;
@@ -0,0 +1,95 @@
1
+ import type { JsonValue, SecretObfuscator } from "./obfuscator.js";
2
+ import { type RegexScanSegment, type ReplaceRegexScan } from "./placeholder.js";
3
+ export declare function transformOutsidePlaceholdersTracked(text: string, origin: string, shouldSkipPlaceholder: (placeholder: string) => boolean, transform: (chunk: string) => string, preservePlaceholder?: (placeholder: string) => string): {
4
+ text: string;
5
+ origin: string;
6
+ };
7
+ export declare function trailingOutsidePreservedPlaceholderChunk(text: string, shouldPreservePlaceholder: (placeholder: string) => boolean): string;
8
+ export declare function buildReplaceRegexScan(text: string, ranges: ReadonlyArray<{
9
+ start: number;
10
+ end: number;
11
+ }>, deobfuscateMap: ReadonlyMap<string, {
12
+ secret: string;
13
+ recursive: boolean;
14
+ }>): ReplaceRegexScan;
15
+ export declare function mapReplaceRegexMatch(segments: ReadonlyArray<RegexScanSegment>, scanStart: number, scanEnd: number): {
16
+ start: number;
17
+ end: number;
18
+ recursive: boolean;
19
+ preserveGeneratedPlaceholders: boolean;
20
+ partialPlaceholderCut: boolean;
21
+ cutResumeIndex: number;
22
+ firstPlaceholderScanStart: number;
23
+ };
24
+ /**
25
+ * Extend a scan-space resume position past a consecutive run of generated
26
+ * placeholder segments starting exactly at it, with no raw gap in between. A
27
+ * cut-resolution resume point that happens to land precisely on the START of
28
+ * ANOTHER placeholder must not stop there and hand it to a fresh `regex.exec`
29
+ * attempt — the same content, scanned as an opaque adjacent placeholder run,
30
+ * must resolve identically whether the run's LEADING member is still raw text
31
+ * (this call is about to placeholder it) or is ALREADY a placeholder from a
32
+ * prior call or an earlier pass of this same call. Without this, a bounded
33
+ * regex whose reach spans two adjacent secrets plus trailing spillover bytes
34
+ * (e.g. `[A-Z]{9}` over `ABCDEFGH` + `SECRETUV` + `A`) resolves the leading
35
+ * secret as its own independent redaction on the FIRST obfuscate() call (a
36
+ * genuinely raw prefix gets its own match, then the discard for the rest
37
+ * resumes right after it), but on a LATER call — once that prefix is itself a
38
+ * placeholder — the very first match attempt starts already inside the
39
+ * placeholder run, cannot be prefix-narrowed at all, and its discard resume
40
+ * point lands mid-run instead of past it, exposing a shorter tail (`SECRETUV`
41
+ * + `A`) to a clean, un-cut match the first call never attempted. Chaining the
42
+ * resume point through every immediately-adjacent placeholder makes both
43
+ * calls land on the exact same next scan position.
44
+ */
45
+ export declare function extendPastAdjacentPlaceholders(segments: ReadonlyArray<RegexScanSegment>, index: number): number;
46
+ export declare function redactWithFixedReplacementOutsidePlaceholders(text: string, origin: string, replacement: string, shouldPreservePlaceholder: (placeholder: string) => boolean): {
47
+ text: string;
48
+ origin: string;
49
+ };
50
+ export declare function deobfuscateGeneratedPlaceholderRanges(text: string, start: number, end: number, ranges: ReadonlyArray<{
51
+ start: number;
52
+ end: number;
53
+ }>, deobfuscateMap: ReadonlyMap<string, {
54
+ secret: string;
55
+ recursive: boolean;
56
+ }>): {
57
+ text: string;
58
+ recursive: boolean;
59
+ };
60
+ export declare function placeholderInnerText(text: string, start: number, end: number, ranges: ReadonlyArray<{
61
+ start: number;
62
+ end: number;
63
+ }>, deobfuscateMap: ReadonlyMap<string, {
64
+ secret: string;
65
+ recursive: boolean;
66
+ }>): string;
67
+ export declare function textOutsidePlaceholderRanges(text: string, start: number, end: number, ranges: ReadonlyArray<{
68
+ start: number;
69
+ end: number;
70
+ }>): string;
71
+ export declare function outsidePlaceholderRangesAnyIndependentlyMatch(text: string, scanText: string, segments: ReadonlyArray<RegexScanSegment>, start: number, end: number, ranges: ReadonlyArray<{
72
+ start: number;
73
+ end: number;
74
+ }>, regex: RegExp): boolean;
75
+ export declare function firstOutsidePlaceholderRange(start: number, end: number, ranges: ReadonlyArray<{
76
+ start: number;
77
+ end: number;
78
+ }>): {
79
+ start: number;
80
+ end: number;
81
+ } | undefined;
82
+ export declare function countOutsidePlaceholderRanges(start: number, end: number, ranges: ReadonlyArray<{
83
+ start: number;
84
+ end: number;
85
+ }>): number;
86
+ export declare function replaceRange(text: string, start: number, end: number, replacement: string): string;
87
+ /** Deep-walk an object, transforming all string values. */
88
+ export declare function deepWalkStrings<T>(obj: T, transform: (s: string) => string): T;
89
+ export declare function collectJsonRegexSecretValues(obfuscator: SecretObfuscator, value: JsonValue): Set<string>;
90
+ /**
91
+ * Map every string in arbitrary JSON. Used ONLY for tool-call arguments, whose
92
+ * shape is model-authored and not known ahead of time. No other caller may walk
93
+ * untyped data: every message/content path is handled by a typed transformer.
94
+ */
95
+ export declare function mapJsonStrings(value: JsonValue, fn: (s: string) => string): JsonValue;
@@ -0,0 +1,94 @@
1
+ import type { SecretEntry } from "./obfuscator.js";
2
+ export declare const MIN_OBFUSCATE_SECRET_LEN = 8;
3
+ export declare function defaultPlaceholderKey(): string;
4
+ type PlaceholderCaseHint = "U" | "L" | "C" | "M";
5
+ /** Normalize a friendly name into the model-visible placeholder prefix. */
6
+ export declare function sanitizeSecretFriendlyName(name: string): string | undefined;
7
+ /**
8
+ * Normalize a secret value into the same alnum-only, uppercased shape a
9
+ * friendly-name label or placeholder prefix is sanitized into, so comparing a
10
+ * raw (possibly lowercase/punctuated) secret value against already-sanitized,
11
+ * model-visible text does not miss a case- or separator-only variant. Unlike
12
+ * `sanitizeSecretFriendlyName` this never truncates and never signals "empty"
13
+ * via `undefined` — callers already guard on `.length > 0` before comparing.
14
+ */
15
+ export declare function sanitizeForCollisionCheck(value: string): string;
16
+ export declare function sanitizedLabelCollidesWithSecret(sanitizedLabel: string, sanitizedSecret: string): boolean;
17
+ /**
18
+ * Whether an entry needs the persisted placeholder key: either because it can
19
+ * produce a reversible (keyed) obfuscate-mode placeholder, or because a default
20
+ * (no custom `replacement`) replace-mode regex can reach
21
+ * `#generateRegexReplacement`'s key-derived idempotent fallback marker (see
22
+ * `#generateReplacement`) when every same-length candidate re-matches a
23
+ * pathological match-everything config (e.g. `[\s\S]{8}`). That fallback depends
24
+ * on the persisted per-install key — not just length — to stay a fixed point
25
+ * across a process restart; without a persisted key, a fresh install falls back
26
+ * to a process-random key (`defaultPlaceholderKey()`), so the fallback marker
27
+ * would churn across restarts even though the algorithm itself is stable. A
28
+ * regex WITH a custom `replacement` never reaches that fallback (it always emits
29
+ * the literal configured string), and a plain replace secret's replacement is
30
+ * pure content-hash (`#generateSecretReplacement`), so neither needs the key.
31
+ * Short plain obfuscate entries are toned down (never placeheld), so they must
32
+ * NOT force key creation: otherwise a `secret-placeholder.key` file is written
33
+ * and persisted for a config that ends up with no active secrets, leaving the
34
+ * key readable via a tool and reusable for later placeholders.
35
+ */
36
+ export declare function secretEntryNeedsPlaceholderKey(entry: SecretEntry): boolean;
37
+ /**
38
+ * Whether a SET of entries needs the persisted placeholder key. `obfuscate()`
39
+ * applies plain replace-mode mappings before the plain-obfuscate pass, so a plain
40
+ * obfuscate entry only emits a reversible (keyed) placeholder when its content can
41
+ * still appear AFTER the replace phase. When no obfuscate entry can ever produce a
42
+ * placeholder, the persisted key must NOT be required/created — otherwise an
43
+ * effectively replace-only secret set still writes `secret-placeholder.key` and
44
+ * fails startup when the agent config dir is unwritable.
45
+ *
46
+ * The decision models the replace phase as the obfuscator actually runs it:
47
+ * replace mappings are content-keyed (later duplicate wins) and applied in
48
+ * descending content-length order; for a fresh probe (no prior placeholders) that
49
+ * phase is plain sequential substring replacement. A plain obfuscate entry needs
50
+ * the key when its content survives that simulated phase (direct typing) OR when
51
+ * any effective replacement can form the content via tiling — a substring,
52
+ * wholesale superstring, or prefix/suffix border that joins with surrounding
53
+ * passthrough bytes (see `replacementCanFormContent`). This covers direct
54
+ * shadowing (`SECRET -> safe`), reintroduction, duplicate ordering, transitive
55
+ * chains, and context-joined fragments uniformly. Default (omitted) replacements
56
+ * are deterministic, length-preserving, and distinct, so a same-content shadow
57
+ * with no other interacting replacement stays key-free.
58
+ * Replacement outputs are themselves rewritten by every later (shorter-content)
59
+ * replacement before the plain-obfuscate pass sees them, so a fragment that a
60
+ * subsequent replacement erases (`AA -> SEC` then `S -> X` turns every `SEC` into
61
+ * `XEC`) no longer forces the key. Surrounding bytes stay modeled as arbitrary
62
+ * passthrough, so testing the surviving fragment only drops false positives and
63
+ * never under-approximates a real key need.
64
+ */
65
+ export declare function secretEntriesNeedPlaceholderKey(entries: SecretEntry[]): boolean;
66
+ export declare function buildHashBase(key: string, value: string): string;
67
+ export declare function buildKeyedReplacementRun(key: string, length: number): string;
68
+ export declare function inferCaseHint(secret: string): PlaceholderCaseHint | undefined;
69
+ export declare function buildPlaceholder(hint: PlaceholderCaseHint | undefined, base: string, friendlyName?: string): string;
70
+ /** Regex matching `$$HASH$$`, `$$HASH:U$$`, and `$$FRIENDLY_HASH(:hint)$$` placeholders. */
71
+ export declare const PLACEHOLDER_RE: RegExp;
72
+ export declare function resumePlaceholderScanAfterRejectedCandidate(match: RegExpExecArray): void;
73
+ export declare function placeholderWithoutFriendlyName(placeholder: string): string | undefined;
74
+ export declare function lookupFriendlyPlaceholderAlias(deobfuscateMap: ReadonlyMap<string, {
75
+ secret: string;
76
+ recursive: boolean;
77
+ }>, placeholder: string): {
78
+ secret: string;
79
+ recursive: boolean;
80
+ } | undefined;
81
+ export declare function stripPendingSecretPlaceholderSuffix(text: string): string;
82
+ export interface RegexScanSegment {
83
+ scanStart: number;
84
+ scanEnd: number;
85
+ textStart: number;
86
+ textEnd: number;
87
+ generatedPlaceholder: boolean;
88
+ recursive: boolean;
89
+ }
90
+ export interface ReplaceRegexScan {
91
+ text: string;
92
+ segments: RegexScanSegment[];
93
+ }
94
+ export {};
@@ -0,0 +1,82 @@
1
+ export declare const REPLACEMENT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2
+ /** Generate a deterministic same-length replacement string from a secret value. */
3
+ export declare function generateDeterministicReplacement(secret: string): string;
4
+ /**
5
+ * Force a length-preserving deterministic replacement to differ from the secret
6
+ * it stands in for. `generateDeterministicReplacement` seeds its first 1–2 chars
7
+ * with the `Z`/`ZZ` sentinel, so a whole configured value that is exactly `Z` or
8
+ * `ZZ` (or an astronomically unlikely longer hash collision) would otherwise be
9
+ * emitted unchanged and ship the raw secret to the provider. Flip the first char
10
+ * to a fixed different glyph: same length, still deterministic, guaranteed != the
11
+ * secret. Only safe for a whole CONFIGURED value (a plain secret matches its own
12
+ * literal, so the perturbed output is no longer matched and stays a fixed point);
13
+ * per-chunk remainders must keep the sentinel to remain idempotent across restart.
14
+ */
15
+ export declare function ensureDistinctReplacement(replacement: string, secret: string): string;
16
+ export interface RegexMatchContext {
17
+ /** Full text the match was found in (positions are offsets into it). */
18
+ text: string;
19
+ /** Start/end of the matched span being replaced. */
20
+ start: number;
21
+ end: number;
22
+ }
23
+ /**
24
+ * Whether `candidate`, substituted for the matched span in its surrounding text,
25
+ * is re-matched by `regex` at its own position. A replace-mode regex that depends
26
+ * on context (lookbehind/lookahead/`\b`) can match a candidate that does NOT match
27
+ * in isolation: e.g. `(?<=api=)[AZ]` never matches a bare `A`, but `api=A` does, so
28
+ * a candidate `A` chosen by an isolation test is re-redacted on the next obfuscate()
29
+ * pass and can oscillate back to the raw matched value. The probe substitutes the
30
+ * candidate into the FULL text — not a truncated window — so a wide lookbehind or
31
+ * lookahead (e.g. `(?<=A{600})`) still evaluates against the context that makes it
32
+ * match. Truncating that context dropped the assertion's reach and falsely
33
+ * accepted an oscillating, leaky candidate. The scan starts a bounded distance
34
+ * left of the span and stops once a match begins at/after the span's end (matches
35
+ * arrive in order), keeping per-candidate cost independent of total text length.
36
+ */
37
+ export declare function regexRematchesInContext(candidate: string, regex: RegExp, ctx: RegexMatchContext): boolean;
38
+ /**
39
+ * Search same-length replacements for one the regex does NOT match, so a default
40
+ * regex secret whose deterministic replacement collides with its own value (the
41
+ * `Z`/`ZZ` sentinel, or an astronomical hash collision) is still redacted to a
42
+ * STABLE nonmatching value instead of shipping the raw secret. A nonmatching
43
+ * candidate is a fixed point under re-obfuscation — the regex never re-matches it,
44
+ * so it cannot re-leak on a later pass. The search stays bounded to O(length *
45
+ * alphabet) regardless of value length: first exhaust every single-position
46
+ * substitution against a deterministic baseline (`AAAA…`, then `!AAA…`, `A!AA…`,
47
+ * …) so any regex that only needs one out-of-class byte — regardless of position —
48
+ * is found in a handful of probes rather than enumerating every combination (which
49
+ * for a 3-byte match-everything config, e.g. `[\s\S]{3}`, would otherwise run
50
+ * 90**3 = 729000 candidates through the regex on every single match, stalling
51
+ * provider requests). Candidates are enumerated deterministically over a stable
52
+ * ASCII alphabet: alphanumerics first (usually enough), then punctuation fallback
53
+ * bytes when the regex covers every alphanumeric candidate. When the regex still
54
+ * matches around a lone perturbed byte (for example `[A-Za-z0-9].*` matching the
55
+ * unperturbed tail), full-width same-byte candidates (`!!!!!`, `_____`, …) are
56
+ * tried next. When the regex covers every non-whitespace candidate (e.g. `\S{n}`),
57
+ * whitespace markers (a full space/tab run, then a single whitespace byte among
58
+ * non-whitespace filler) are tried as a last resort. A genuine match-everything
59
+ * regex (`.`/`[\s\S]`, which also matches space and tab) still exhausts this bounded
60
+ * sweep and returns undefined, letting the caller keep its own fixed-point fallback
61
+ * — bounded search can in principle miss an escape that depends jointly on
62
+ * multiple positions in a way no single-position swap reaches, but no realistic
63
+ * secret-redaction regex (character classes, literal matches, anchored/bounded
64
+ * repeats) has that shape.
65
+ */
66
+ export declare function findNonMatchingReplacement(value: string, regex: RegExp, context: RegexMatchContext): string | undefined;
67
+ /**
68
+ * Whether a default (no custom `replacement`) replace-mode regex can never
69
+ * safely redact a 1-2 char match: `findNonMatchingReplacement`'s bounded
70
+ * search — the same search `#generateRegexReplacement` runs at match time —
71
+ * finds no candidate the regex fails to re-match. This holds independent of
72
+ * any actual per-install key: the search already exhausts every character in
73
+ * `REPLACEMENT_CHARS` (the alphabet `buildKeyedReplacementRun` draws its
74
+ * fallback marker from) plus punctuation and whitespace, so if none of those
75
+ * escape the regex, no key-derived marker drawn from the same alphabet can
76
+ * either — the marker is guaranteed to re-match too, making every such match
77
+ * unresolvable: the fallback could only ever emit the raw matched text
78
+ * unchanged. Probed with a value (`"\0".repeat(length)`) the bounded search
79
+ * never treats as a real candidate, so the result depends only on the
80
+ * regex's own matching behavior, not on this specific probe.
81
+ */
82
+ export declare function regexHasUnresolvableShortMatchFallback(regex: RegExp): boolean;
@@ -24,6 +24,12 @@ export declare const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1500;
24
24
  /** Options controlling session disposal. */
25
25
  export interface AgentSessionDisposeOptions {
26
26
  mnemopiConsolidateTimeoutMs?: number;
27
+ /**
28
+ * Deadline for the settle/drain wait before the terminal memory release
29
+ * (default 5s). The bounded-teardown paths (signal handlers, tests) may
30
+ * shorten it; late event handlers are still finalized after they settle.
31
+ */
32
+ drainTimeoutMs?: number;
27
33
  /**
28
34
  * Postmortem reason that triggered this dispose (signal/fatal teardown
29
35
  * paths). When set, the persisted `session_exit` diagnostic records it
@@ -41,7 +41,7 @@ import type { DaemonCompletionNotification } from "../launch/protocol.js";
41
41
  import { type MnemopiSessionState } from "../mnemopi/state.js";
42
42
  import { type PlanApprovalDetails } from "../plan-mode/approved-plan.js";
43
43
  import type { PlanModeState } from "../plan-mode/state.js";
44
- import { type SecretObfuscator } from "../secrets/obfuscator.js";
44
+ import type { SecretObfuscator } from "../secrets/obfuscator.js";
45
45
  import { type ConfiguredThinkingLevel } from "../thinking.js";
46
46
  import { type AskToolDetails, type AskToolInput } from "../tools/ask.js";
47
47
  import type { CheckpointState, CompletedRewindState } from "../tools/checkpoint.js";
@@ -4,7 +4,7 @@ import type { Message, Model, ServiceTier, SimpleStreamOptions } from "@oh-my-pi
4
4
  import type { ModelRegistry } from "../config/model-registry.js";
5
5
  import type { Settings } from "../config/settings.js";
6
6
  import type { ExtensionRunner } from "../extensibility/extensions/index.js";
7
- import { type SecretObfuscator } from "../secrets/obfuscator.js";
7
+ import type { SecretObfuscator } from "../secrets/obfuscator.js";
8
8
  import type { HandoffResult, SessionHandoffOptions } from "./agent-session-types.js";
9
9
  import type { BashSessionTransition } from "./bash-runner.js";
10
10
  import type { SessionContext } from "./session-context.js";
@@ -55,9 +55,9 @@ export declare class SessionHandoff {
55
55
  #private;
56
56
  constructor(host: SessionHandoffHost);
57
57
  /**
58
- * Cancel in-progress handoff generation.
58
+ * Cancel in-progress handoff generation, preserving a harness-provided reason.
59
59
  */
60
- abortHandoff(): void;
60
+ abortHandoff(reason?: Error): void;
61
61
  /**
62
62
  * Check if handoff generation is in progress.
63
63
  */
@@ -133,6 +133,38 @@ export declare class SessionManager {
133
133
  flushSync(): void;
134
134
  /** Flush, then close the append writer. */
135
135
  close(): Promise<void>;
136
+ /**
137
+ * Raise the terminal write barrier ahead of the final {@link close}. Once
138
+ * sealed:
139
+ * - every later append, title change, and rewrite is a dropped no-op —
140
+ * including work an event handler tries to enqueue while dispose is
141
+ * awaiting `close()` on the disk tail;
142
+ * - the disk epoch is bumped, so queued-but-unexecuted tail work is
143
+ * superseded and an ALREADY-RUNNING fenced/repair rewrite (awaiting the
144
+ * tail, drain, writer close, or the atomic stage) fails its commit guard
145
+ * at the rename fence instead of publishing over a revived file.
146
+ * The final `close()` itself is scheduled after the bump and still runs;
147
+ * pre-seal hot-path appends are already in the page cache. Idempotent;
148
+ * terminal.
149
+ */
150
+ seal(): void;
151
+ /**
152
+ * Terminal release: drop the in-memory transcript and complete the
153
+ * {@link seal}. The entry journal and its index mirror the agent's message
154
+ * array (tool results, file contents, base64 frame images); on a disposed
155
+ * session — e.g. a parked subagent still referenced by the lifecycle
156
+ * adoption record — they would otherwise stay pinned for the process
157
+ * lifetime.
158
+ *
159
+ * Closes the append writer; with the seal up, nothing can reopen it. A
160
+ * revival may reopen the same JSONL through a NEW manager the moment
161
+ * dispose returns; a late event handler resuming on THIS manager must
162
+ * never race that writer — and a post-release rewrite would persist the
163
+ * now-empty entry list, truncating the transcript. Reads after this point
164
+ * reopen from disk (revival, `history://`). Only call from session
165
+ * dispose, after the final `close()`; idempotent.
166
+ */
167
+ releaseRetainedEntries(): void;
136
168
  getCwd(): string;
137
169
  /** Additional workspace directories beyond cwd (multi-root), absolute and normalized. */
138
170
  getAdditionalDirectories(): string[];
@@ -5,7 +5,7 @@ import type { ImageContent, Message, Model, SimpleStreamOptions } from "@oh-my-p
5
5
  import type { ModelRegistry } from "../config/model-registry.js";
6
6
  import type { Settings } from "../config/settings.js";
7
7
  import type { LocalProtocolOptions } from "../internal-urls/index.js";
8
- import { type SecretObfuscator } from "../secrets/obfuscator.js";
8
+ import type { SecretObfuscator } from "../secrets/obfuscator.js";
9
9
  import { type CustomMessage } from "./messages.js";
10
10
  import type { BuildSessionContextOptions, SessionContext } from "./session-context.js";
11
11
  import type { SessionManager } from "./session-manager.js";
@@ -156,9 +156,9 @@ export declare class TurnRecovery {
156
156
  */
157
157
  isRetryableReasonlessAbort(message: AssistantMessage): boolean;
158
158
  /**
159
- * Check if an error is retryable (transient errors or usage limits).
159
+ * Check if an error is retryable (transient errors, usage limits, or
160
+ * account-scoped policy denials that can rotate credentials).
160
161
  * Context overflow is NOT retryable (handled by compaction instead).
161
- * Usage-limit errors are retryable because the retry handler performs credential switching.
162
162
  */
163
163
  isRetryableError(message: AssistantMessage): boolean;
164
164
  /**
@@ -0,0 +1,2 @@
1
+ import type { SlashCommandSpec } from "./types.js";
2
+ export declare const BUILTIN_COLLABORATION_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;
@@ -0,0 +1,36 @@
1
+ import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
2
+ import type { SubcommandDef, TuiSlashCommandRuntime } from "./types.js";
3
+ /**
4
+ * Build getArgumentCompletions from declarative subcommand definitions.
5
+ * Returns subcommand names filtered by prefix in the dropdown.
6
+ */
7
+ export declare function buildArgumentCompletions(subcommands: SubcommandDef[]): (prefix: string) => AutocompleteItem[] | null;
8
+ /**
9
+ * Build getArgumentCompletions for /mcp. Delegates to the generic
10
+ * declarative subcommand completer while the subcommand name itself is
11
+ * still being typed, then switches to MCP server-name completion (sourced
12
+ * from {@link collectMcpServerNames}) once a recognized server-name
13
+ * subcommand (enable/disable/test/remove/reconnect/reauth/unauth) is
14
+ * followed by a space. `remove` gets its own scope-aware completions (see
15
+ * {@link buildMcpRemoveCompletions}) since — unlike the others —
16
+ * it only ever succeeds against a config-file entry. Subcommands with a
17
+ * different argument shape (add, smithery-search, ...) get no argument
18
+ * completion.
19
+ */
20
+ export declare function buildMcpArgumentCompletions(subcommands: SubcommandDef[], runtime: TuiSlashCommandRuntime): (argumentPrefix: string) => Promise<AutocompleteItem[] | null>;
21
+ /**
22
+ * Build getInlineHint from declarative subcommand definitions.
23
+ * Shows remaining completion + usage as dim ghost text after cursor.
24
+ */
25
+ export declare function buildSubcommandInlineHint(subcommands: SubcommandDef[]): (argumentText: string) => string | null;
26
+ /**
27
+ * Build getInlineHint for commands with a simple static hint string.
28
+ * Shows the hint only when no arguments have been typed yet.
29
+ */
30
+ export declare function buildStaticInlineHint(hint: string): (argumentText: string) => string | null;
31
+ /**
32
+ * Build getArgumentCompletions that suggests directories relative to the
33
+ * current project directory. Used by /move so users can Tab-complete the
34
+ * destination directory.
35
+ */
36
+ export declare function buildDirectoryArgumentCompletions(): (prefix: string) => Promise<AutocompleteItem[] | null>;
@@ -0,0 +1,2 @@
1
+ import type { SlashCommandSpec } from "./types.js";
2
+ export declare const BUILTIN_CONTROL_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;
@@ -0,0 +1,3 @@
1
+ import type { ParsedSlashCommand, SlashCommandResult, SlashCommandSpec, TuiSlashCommandRuntime } from "./types.js";
2
+ export declare const shutdownHandlerTui: (_command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime) => SlashCommandResult;
3
+ export declare const BUILTIN_LIFECYCLE_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;
@@ -0,0 +1,11 @@
1
+ import type { InteractiveModeContext } from "../modes/types.js";
2
+ import type { SlashCommandSpec } from "./types.js";
3
+ /**
4
+ * Reload the interactive session's plugin runtime: invalidate fs/plugin-root
5
+ * caches, rediscover skills, file slash commands, and task agents, reset the
6
+ * capability cache, and reconnect MCP servers (rebinding the session's MCP
7
+ * tools). Shared by `/reload-plugins`'s TUI handler and the `handle`-adapter's
8
+ * `reloadPlugins` hook so both honor the command's documented reload scope.
9
+ */
10
+ export declare function reloadTuiPluginState(ctx: InteractiveModeContext): Promise<void>;
11
+ export declare const BUILTIN_MARKETPLACE_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;
@@ -0,0 +1,5 @@
1
+ import type { InteractiveModeContext } from "../modes/types.js";
2
+ import type { SlashCommandSpec } from "./types.js";
3
+ export declare function refreshStatusLine(ctx: InteractiveModeContext): void;
4
+ export declare function formatTokenCount(value: number): string;
5
+ export declare const BUILTIN_MODE_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;
@@ -1,4 +1,4 @@
1
- import { type AutocompleteItem } from "@oh-my-pi/pi-tui";
1
+ import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
2
2
  import type { BuiltinSlashCommand, ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime, SlashCommandSpec, TuiSlashCommandRuntime } from "./types.js";
3
3
  export type { BuiltinSlashCommand, SubcommandDef } from "./types.js";
4
4
  /** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
@@ -0,0 +1,2 @@
1
+ import type { SlashCommandSpec } from "./types.js";
2
+ export declare const BUILTIN_SESSION_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec>;