@oh-my-pi/pi-coding-agent 16.3.0 → 16.3.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.
- package/CHANGELOG.md +53 -0
- package/dist/cli.js +3610 -3566
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/irc/bus.d.ts +5 -4
- package/dist/types/session/agent-session.d.ts +13 -13
- package/dist/types/task/discovery.d.ts +5 -2
- package/dist/types/task/renderer.d.ts +1 -0
- package/dist/types/tools/ast-grep.d.ts +4 -2
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +2 -0
- package/dist/types/tools/glob.d.ts +4 -2
- package/dist/types/tools/grep.d.ts +4 -3
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -1
- package/dist/types/tts/index.d.ts +2 -0
- package/dist/types/tts/speakable.d.ts +47 -0
- package/dist/types/tts/speech-enhancer.d.ts +46 -0
- package/dist/types/tts/streaming-player.d.ts +1 -2
- package/dist/types/tts/tts-client.d.ts +11 -10
- package/dist/types/tts/tts-protocol.d.ts +7 -0
- package/dist/types/tts/vocalizer.d.ts +15 -8
- package/dist/types/utils/git.d.ts +2 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +2 -2
- package/src/cli/gallery-fixtures/search.ts +2 -2
- package/src/cli.ts +27 -5
- package/src/config/model-resolver.ts +31 -10
- package/src/config/settings-schema.ts +11 -0
- package/src/cursor.ts +1 -1
- package/src/discovery/helpers.ts +8 -0
- package/src/export/html/tool-views.generated.js +34 -34
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/extensions/loader.ts +10 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +2 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +87 -11
- package/src/extensibility/plugins/loader.ts +30 -1
- package/src/irc/bus.ts +5 -4
- package/src/modes/components/__tests__/move-overlay.test.ts +72 -1
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/move-overlay.ts +35 -23
- package/src/modes/components/tool-execution.ts +45 -12
- package/src/modes/components/tree-selector.ts +10 -3
- package/src/modes/controllers/event-controller.ts +16 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +10 -2
- package/src/prompts/system/speech-rewrite.md +15 -0
- package/src/prompts/system/tiny-title-system.md +1 -1
- package/src/prompts/system/title-system-marker.md +2 -1
- package/src/prompts/system/title-system.md +2 -1
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/glob.md +1 -1
- package/src/prompts/tools/grep.md +1 -1
- package/src/sdk.ts +8 -5
- package/src/session/agent-session.ts +163 -49
- package/src/session/session-history-format.ts +6 -2
- package/src/task/discovery.ts +25 -2
- package/src/task/executor.ts +24 -4
- package/src/task/render.ts +26 -12
- package/src/task/renderer.ts +1 -0
- package/src/task/worktree.ts +144 -16
- package/src/tiny/text.ts +109 -17
- package/src/tools/ast-grep.ts +20 -17
- package/src/tools/bash.ts +16 -8
- package/src/tools/browser/render.ts +2 -0
- package/src/tools/debug.ts +1 -0
- package/src/tools/eval-render.ts +5 -2
- package/src/tools/gh-renderer.ts +3 -0
- package/src/tools/glob.ts +24 -20
- package/src/tools/grep.ts +18 -36
- package/src/tools/path-utils.ts +55 -10
- package/src/tools/read.ts +9 -2
- package/src/tools/renderers.ts +11 -0
- package/src/tools/ssh.ts +17 -7
- package/src/tts/index.ts +2 -0
- package/src/tts/speakable.ts +382 -0
- package/src/tts/speech-enhancer.ts +204 -0
- package/src/tts/streaming-player.ts +71 -16
- package/src/tts/tts-client.ts +11 -10
- package/src/tts/tts-protocol.ts +14 -5
- package/src/tts/tts-worker.ts +52 -49
- package/src/tts/vocalizer.ts +277 -46
- package/src/utils/git.ts +8 -0
|
@@ -4752,6 +4752,16 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
4752
4752
|
}];
|
|
4753
4753
|
};
|
|
4754
4754
|
};
|
|
4755
|
+
readonly "speech.enhanced": {
|
|
4756
|
+
readonly type: "boolean";
|
|
4757
|
+
readonly default: false;
|
|
4758
|
+
readonly ui: {
|
|
4759
|
+
readonly tab: "providers";
|
|
4760
|
+
readonly group: "Services";
|
|
4761
|
+
readonly label: "Enhanced Speech Rewriting";
|
|
4762
|
+
readonly description: "Rewrite assistant output into natural spoken prose with the tiny/smol model before synthesis (describes code, drops links and markdown). Falls back to mechanical cleanup on failure";
|
|
4763
|
+
};
|
|
4764
|
+
};
|
|
4755
4765
|
readonly "speech.voice": {
|
|
4756
4766
|
readonly type: "enum";
|
|
4757
4767
|
readonly values: readonly string[];
|
|
@@ -236,6 +236,8 @@ export declare function resolveActiveProjectRegistryPath(cwd: string): Promise<s
|
|
|
236
236
|
* never alias as the project registry.
|
|
237
237
|
*/
|
|
238
238
|
export declare function resolveOrDefaultProjectRegistryPath(cwd: string): Promise<string | undefined>;
|
|
239
|
+
/** Register a process-global plugin cache invalidator called whenever plugin roots are cleared. */
|
|
240
|
+
export declare function registerPluginCacheInvalidator(invalidator: () => void): void;
|
|
239
241
|
/**
|
|
240
242
|
* List all installed Claude Code plugin roots from the plugin cache.
|
|
241
243
|
* Reads ~/.claude/plugins/installed_plugins.json and ~/.omp/plugins/installed_plugins.json,
|
package/dist/types/irc/bus.d.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* agents receive the message as a non-interrupting aside at the next step
|
|
9
9
|
* boundary (see AgentSession.deliverIrcMessage). Replies are real turns by
|
|
10
10
|
* the recipient, observed via `wait` — with one exception: when the sender
|
|
11
|
-
* awaits a reply and the recipient
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* awaits a reply and the recipient cannot run a real reply turn in time
|
|
12
|
+
* (mid-turn with async execution disabled — possibly blocked in a
|
|
13
|
+
* synchronous task spawn whose batch includes the sender — or idle in plan
|
|
14
|
+
* mode, where autonomous wake turns are suppressed), the recipient session
|
|
15
|
+
* generates an ephemeral side-channel auto-reply.
|
|
15
16
|
*/
|
|
16
17
|
import { AgentLifecycleManager } from "../registry/agent-lifecycle";
|
|
17
18
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
@@ -882,12 +882,10 @@ export declare class AgentSession {
|
|
|
882
882
|
/**
|
|
883
883
|
* Set model directly.
|
|
884
884
|
* Validates that a credential source is configured (synchronously, without
|
|
885
|
-
* refreshing OAuth or running command-backed key programs).
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
* model actually switched, computed against the refreshed metadata so
|
|
890
|
-
* dynamic providers (e.g. llama.cpp) honor their post-load contextWindow.
|
|
885
|
+
* refreshing OAuth or running command-backed key programs). Active switches
|
|
886
|
+
* always take effect; if the current transcript is too large for the target
|
|
887
|
+
* model, the next prompt's compaction/error path owns that recovery instead
|
|
888
|
+
* of leaving the session pinned to the old model.
|
|
891
889
|
* @throws Error if no API key available for the model
|
|
892
890
|
*/
|
|
893
891
|
setModel(model: Model, role?: string, options?: {
|
|
@@ -1163,18 +1161,20 @@ export declare class AgentSession {
|
|
|
1163
1161
|
*
|
|
1164
1162
|
* - mid-turn → queued on the aside channel and folded in at the next step
|
|
1165
1163
|
* boundary (non-interrupting, like async-result deliveries) → "injected";
|
|
1164
|
+
* - idle in plan mode → appended into context without waking an autonomous
|
|
1165
|
+
* turn (convergence stays user-driven) → "injected";
|
|
1166
1166
|
* - idle → starts a real turn with the message so the recipient wakes
|
|
1167
1167
|
* → "woken".
|
|
1168
1168
|
*
|
|
1169
1169
|
* Never blocks on the recipient's turn: the wake turn is fire-and-forget.
|
|
1170
1170
|
*
|
|
1171
|
-
* When the sender expects a reply (`send await:true`) and this session
|
|
1172
|
-
*
|
|
1173
|
-
* gated on the sender's own batch
|
|
1174
|
-
*
|
|
1175
|
-
* side-channel auto-reply is generated from the current context
|
|
1176
|
-
* `respondAsBackground` path) and sent back over the bus on this
|
|
1177
|
-
* behalf.
|
|
1171
|
+
* When the sender expects a reply (`send await:true`) and this session
|
|
1172
|
+
* cannot produce a real reply turn in time — mid-turn with async execution
|
|
1173
|
+
* disabled (the next step boundary may be gated on the sender's own batch
|
|
1174
|
+
* finishing), or idle in plan mode (wake turns are suppressed) — an
|
|
1175
|
+
* ephemeral side-channel auto-reply is generated from the current context
|
|
1176
|
+
* (the old `respondAsBackground` path) and sent back over the bus on this
|
|
1177
|
+
* agent's behalf.
|
|
1178
1178
|
*/
|
|
1179
1179
|
deliverIrcMessage(msg: IrcMessage, opts?: {
|
|
1180
1180
|
expectsReply?: boolean;
|
|
@@ -6,8 +6,11 @@ export interface DiscoveryResult {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Discover agents from filesystem and merge with bundled agents.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Precedence (highest wins): project `.omp/agents`, user `.omp/agents`,
|
|
10
|
+
* OMP extension-package agents in `listOmpExtensionRoots` source order
|
|
11
|
+
* (CLI roots > project `extensions:` settings > user `extensions:` settings >
|
|
12
|
+
* installed npm/link plugins), Claude marketplace plugin agents (project
|
|
13
|
+
* scope before user), then bundled.
|
|
11
14
|
* @param cwd - Current working directory for project agent discovery
|
|
12
15
|
*/
|
|
13
16
|
export declare function discoverAgents(cwd: string, home?: string): Promise<DiscoveryResult>;
|
|
@@ -7,7 +7,7 @@ import type { ToolSession } from ".";
|
|
|
7
7
|
import type { OutputMeta } from "./output-meta";
|
|
8
8
|
declare const astGrepSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
9
9
|
pat: string;
|
|
10
|
-
|
|
10
|
+
path?: string | undefined;
|
|
11
11
|
skip?: number | undefined;
|
|
12
12
|
}, {}>;
|
|
13
13
|
export interface AstGrepToolDetails {
|
|
@@ -44,7 +44,7 @@ export declare class AstGrepTool implements AgentTool<typeof astGrepSchema, AstG
|
|
|
44
44
|
readonly description: string;
|
|
45
45
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
46
46
|
pat: string;
|
|
47
|
-
|
|
47
|
+
path?: string | undefined;
|
|
48
48
|
skip?: number | undefined;
|
|
49
49
|
}, {}>;
|
|
50
50
|
readonly strict = true;
|
|
@@ -55,6 +55,8 @@ export declare class AstGrepTool implements AgentTool<typeof astGrepSchema, AstG
|
|
|
55
55
|
}
|
|
56
56
|
interface AstGrepRenderArgs {
|
|
57
57
|
pat?: string;
|
|
58
|
+
path?: string | string[];
|
|
59
|
+
/** Legacy pre-`path` argument name; kept so historical transcripts still render a scope. */
|
|
58
60
|
paths?: string[];
|
|
59
61
|
skip?: number;
|
|
60
62
|
}
|
|
@@ -35,6 +35,8 @@ interface BrowserRenderContext {
|
|
|
35
35
|
previewLines?: number;
|
|
36
36
|
}
|
|
37
37
|
export declare const browserToolRenderer: {
|
|
38
|
+
animatedPendingPreview: (args: unknown) => boolean;
|
|
39
|
+
animatedPartialResult: (args: unknown) => boolean;
|
|
38
40
|
renderCall(args: BrowserRenderArgs, options: RenderResultOptions, theme: Theme): Component;
|
|
39
41
|
renderResult(result: {
|
|
40
42
|
content: Array<{
|
|
@@ -85,6 +85,7 @@ interface DebugToolDetails {
|
|
|
85
85
|
interface DebugRenderArgs extends Partial<DebugParams> {
|
|
86
86
|
}
|
|
87
87
|
export declare const debugToolRenderer: {
|
|
88
|
+
animatedPartialResult: boolean;
|
|
88
89
|
renderCall(args: DebugRenderArgs, _options: RenderResultOptions, theme: Theme): Component;
|
|
89
90
|
renderResult(result: {
|
|
90
91
|
content: Array<{
|
|
@@ -40,6 +40,8 @@ interface EvalRenderContext {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void;
|
|
42
42
|
export declare const evalToolRenderer: {
|
|
43
|
+
animatedPendingPreview: boolean;
|
|
44
|
+
animatedPartialResult: boolean;
|
|
43
45
|
renderCall(args: EvalRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
|
|
44
46
|
renderResult(result: {
|
|
45
47
|
content: Array<{
|
|
@@ -7,7 +7,7 @@ import { type TruncationResult } from "../session/streaming-output";
|
|
|
7
7
|
import type { ToolSession } from ".";
|
|
8
8
|
import { type OutputMeta } from "./output-meta";
|
|
9
9
|
declare const findSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
10
|
-
|
|
10
|
+
path?: string | undefined;
|
|
11
11
|
hidden?: boolean | undefined;
|
|
12
12
|
gitignore?: boolean | undefined;
|
|
13
13
|
limit?: number | undefined;
|
|
@@ -64,7 +64,7 @@ export declare class GlobTool implements AgentTool<typeof findSchema, GlobToolDe
|
|
|
64
64
|
readonly label = "Glob";
|
|
65
65
|
readonly description: string;
|
|
66
66
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
67
|
-
|
|
67
|
+
path?: string | undefined;
|
|
68
68
|
hidden?: boolean | undefined;
|
|
69
69
|
gitignore?: boolean | undefined;
|
|
70
70
|
limit?: number | undefined;
|
|
@@ -75,6 +75,8 @@ export declare class GlobTool implements AgentTool<typeof findSchema, GlobToolDe
|
|
|
75
75
|
execute(_toolCallId: string, params: typeof findSchema.infer, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<GlobToolDetails>, _context?: AgentToolContext): Promise<AgentToolResult<GlobToolDetails>>;
|
|
76
76
|
}
|
|
77
77
|
interface GlobRenderArgs {
|
|
78
|
+
path?: string | string[];
|
|
79
|
+
/** Legacy pre-`path` argument name; kept so historical transcripts still render a scope. */
|
|
78
80
|
paths?: string | string[];
|
|
79
81
|
limit?: number;
|
|
80
82
|
}
|
|
@@ -7,13 +7,12 @@ import type { ToolSession } from ".";
|
|
|
7
7
|
import type { OutputMeta } from "./output-meta";
|
|
8
8
|
declare const searchSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
9
9
|
pattern: string;
|
|
10
|
-
|
|
10
|
+
path?: string | undefined;
|
|
11
11
|
case?: boolean | undefined;
|
|
12
12
|
gitignore?: boolean | undefined;
|
|
13
13
|
skip?: number | null | undefined;
|
|
14
14
|
}, {}>;
|
|
15
15
|
export type GrepToolInput = typeof searchSchema.infer;
|
|
16
|
-
export declare function toPathList(input: string | string[] | undefined): string[];
|
|
17
16
|
/** Maximum number of distinct files surfaced in a single response. The
|
|
18
17
|
* agent paginates further pages via `skip`. */
|
|
19
18
|
export declare const DEFAULT_FILE_LIMIT = 20;
|
|
@@ -66,7 +65,7 @@ export declare class GrepTool implements AgentTool<typeof searchSchema, GrepTool
|
|
|
66
65
|
readonly description: string;
|
|
67
66
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
68
67
|
pattern: string;
|
|
69
|
-
|
|
68
|
+
path?: string | undefined;
|
|
70
69
|
case?: boolean | undefined;
|
|
71
70
|
gitignore?: boolean | undefined;
|
|
72
71
|
skip?: number | null | undefined;
|
|
@@ -77,6 +76,8 @@ export declare class GrepTool implements AgentTool<typeof searchSchema, GrepTool
|
|
|
77
76
|
}
|
|
78
77
|
interface GrepRenderArgs {
|
|
79
78
|
pattern: string;
|
|
79
|
+
path?: string | string[];
|
|
80
|
+
/** Legacy pre-`path` argument name; kept so historical transcripts still render a scope. */
|
|
80
81
|
paths?: string | string[];
|
|
81
82
|
case?: boolean;
|
|
82
83
|
gitignore?: boolean;
|
|
@@ -117,6 +117,13 @@ export declare function formatPathRelativeToCwd(filePath: string, cwd: string, o
|
|
|
117
117
|
*/
|
|
118
118
|
export declare function stripOuterDoubleQuotes(input: string): string;
|
|
119
119
|
export declare function normalizePathLikeInput(input: string): string;
|
|
120
|
+
/**
|
|
121
|
+
* Normalize a path argument that may arrive as a single string, a JSON-encoded
|
|
122
|
+
* string array (`'["a.ts"]'`), or an actual array into a flat `string[]`.
|
|
123
|
+
* Delimited single strings (`"a.ts b.ts"`) are left for
|
|
124
|
+
* {@link expandDelimitedPathEntries} to split.
|
|
125
|
+
*/
|
|
126
|
+
export declare function toPathList(input: string | string[] | undefined): string[];
|
|
120
127
|
export declare function hasGlobPathChars(filePath: string): boolean;
|
|
121
128
|
type PathEntrySplitter = (item: string) => {
|
|
122
129
|
basePath: string;
|
|
@@ -41,5 +41,16 @@ export type ToolRenderer = {
|
|
|
41
41
|
* with the final render and may commit like any settled stream.
|
|
42
42
|
*/
|
|
43
43
|
provisionalPartialResult?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the renderer's pending-call path visibly consumes
|
|
46
|
+
* `options.spinnerFrame`. Used to avoid scheduling repaint ticks for live
|
|
47
|
+
* partial calls whose bytes cannot change between spinner frames.
|
|
48
|
+
*/
|
|
49
|
+
animatedPendingPreview?: boolean | ((args: unknown) => boolean);
|
|
50
|
+
/**
|
|
51
|
+
* Whether the renderer's partial-result path visibly consumes
|
|
52
|
+
* `options.spinnerFrame`.
|
|
53
|
+
*/
|
|
54
|
+
animatedPartialResult?: boolean | ((args: unknown) => boolean);
|
|
44
55
|
};
|
|
45
56
|
export declare const toolRenderers: Record<string, ToolRenderer>;
|
|
@@ -55,7 +55,8 @@ interface SshRenderContext {
|
|
|
55
55
|
totalVisualLines?: number;
|
|
56
56
|
}
|
|
57
57
|
export declare const sshToolRenderer: {
|
|
58
|
-
|
|
58
|
+
animatedPendingPreview: boolean;
|
|
59
|
+
renderCall(args: SshRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
|
|
59
60
|
renderResult(result: {
|
|
60
61
|
content: Array<{
|
|
61
62
|
type: string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming markdown → speakable-segment transform for assistant speech.
|
|
3
|
+
*
|
|
4
|
+
* Sits between the assistant's raw streaming text deltas and the TTS engine,
|
|
5
|
+
* deciding both *what* is worth speaking and *when* a piece of text is ready
|
|
6
|
+
* to synthesize. Three passes:
|
|
7
|
+
*
|
|
8
|
+
* 1. Block pass (per character, stateful): drops fenced code blocks and table
|
|
9
|
+
* rows, strips heading/bullet/blockquote markers (numbered-list markers are
|
|
10
|
+
* spoken as "1, …"), and turns newlines into hard segment breaks.
|
|
11
|
+
* 2. Segmentation (stateful): emits a segment the moment a sentence boundary
|
|
12
|
+
* appears — no next-sentence confirmation, which is what made the previous
|
|
13
|
+
* engine-side splitter stall a full sentence behind generation. The first
|
|
14
|
+
* segment cuts early at a clause boundary for fast time-to-first-audio, and
|
|
15
|
+
* over-long unpunctuated runs are force-split so no segment exceeds the
|
|
16
|
+
* synthesizer's input budget.
|
|
17
|
+
* 3. Inline normalization (per segment): markdown links speak their label,
|
|
18
|
+
* bare URLs speak their host, inline-code ticks and emphasis markers are
|
|
19
|
+
* stripped, multi-directory file paths collapse to their basename, HTML
|
|
20
|
+
* tags are dropped, and whitespace is collapsed. Segments with no letters
|
|
21
|
+
* or digits left are not spoken at all.
|
|
22
|
+
*
|
|
23
|
+
* Pure and synchronous — the vocalizer owns timers (idle flush) and the
|
|
24
|
+
* session lifecycle, so this class stays trivially unit-testable.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* One per utterance. Feed raw assistant deltas through {@link push}; each call
|
|
28
|
+
* returns the segments that became ready to speak. {@link flush} drains the
|
|
29
|
+
* remainder at message end; {@link flushIdle} drains it when generation stalls
|
|
30
|
+
* mid-sentence so speech doesn't sit on buffered text through a tool call.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SpeakableStream {
|
|
33
|
+
#private;
|
|
34
|
+
/** Consume a raw delta; returns segments now ready to speak, in order. */
|
|
35
|
+
push(delta: string): string[];
|
|
36
|
+
/** Message end: drain everything left, including a trailing partial sentence. */
|
|
37
|
+
flush(): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Generation stalled (tool call, thinking block): speak what we have rather
|
|
40
|
+
* than sit silent on buffered text. Keeps block state so the stream resumes
|
|
41
|
+
* afterwards, and refuses stubby mid-sentence fragments — the buffer must be
|
|
42
|
+
* a complete thought (trailing sentence punctuation) or at least
|
|
43
|
+
* {@link MIN_SEGMENT} long, so a stall right after "The" stays silent
|
|
44
|
+
* instead of turning into choppy one-word speech.
|
|
45
|
+
*/
|
|
46
|
+
flushIdle(): string[];
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
2
|
+
import type { Settings } from "../config/settings";
|
|
3
|
+
/** Session-scoped dependencies; mirrors the auto-thinking classifier's deps. */
|
|
4
|
+
export interface SpeechEnhancerDeps {
|
|
5
|
+
settings: Settings;
|
|
6
|
+
registry: ModelRegistry;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Rewrites one markdown block into spoken prose via the tiny/smol role.
|
|
12
|
+
* Constructed per session by the event controller and handed to the vocalizer.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SpeechEnhancer {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(deps: SpeechEnhancerDeps);
|
|
17
|
+
/**
|
|
18
|
+
* Rewrite `block` for speech. Returns the spoken text (empty string when
|
|
19
|
+
* the model judged the block unspeakable — pure code/markup), or null when
|
|
20
|
+
* the rewrite failed, timed out, or no model/key resolved; the caller then
|
|
21
|
+
* falls back to mechanical normalization.
|
|
22
|
+
*/
|
|
23
|
+
rewrite(block: string, signal?: AbortSignal): Promise<string | null>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fence-aware paragraph accumulator over raw streaming deltas. One instance
|
|
27
|
+
* per utterance.
|
|
28
|
+
*/
|
|
29
|
+
export declare class BlockAccumulator {
|
|
30
|
+
#private;
|
|
31
|
+
/** Feed a delta; returns the blocks it completed, in order. */
|
|
32
|
+
push(delta: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Message end: drain everything. An unterminated code fence is dropped from
|
|
35
|
+
* its opening line onward (a truncated block is never worth speaking); the
|
|
36
|
+
* prose before it still comes out.
|
|
37
|
+
*/
|
|
38
|
+
flush(): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Generation stalled: drain the pending partial block — unless we are
|
|
41
|
+
* inside a code fence, where the only thing buffered is code and speaking
|
|
42
|
+
* or rewriting half a fence would re-introduce vocalized code. Fence state
|
|
43
|
+
* is preserved so the eventual closing fence still matches.
|
|
44
|
+
*/
|
|
45
|
+
flushPartial(): string | null;
|
|
46
|
+
}
|
|
@@ -12,8 +12,7 @@ export interface StreamingPlayerLookup {
|
|
|
12
12
|
* and plays it to the default output device. An empty list means no streaming
|
|
13
13
|
* backend is available and the caller should fall back to per-file playback.
|
|
14
14
|
*
|
|
15
|
-
* - darwin:
|
|
16
|
-
* per-file fallback.
|
|
15
|
+
* - darwin: `ffmpeg` (AudioToolbox output device) → sox's `play` (coreaudio).
|
|
17
16
|
* - linux/other POSIX: `ffmpeg` (`-f pulse` then `-f alsa`) → `paplay`/`aplay`
|
|
18
17
|
* raw fallbacks.
|
|
19
18
|
* - win32: none (PowerShell `SoundPlayer` is file-only).
|
|
@@ -17,7 +17,7 @@ export interface TtsStreamOptions {
|
|
|
17
17
|
voice?: string;
|
|
18
18
|
signal?: AbortSignal;
|
|
19
19
|
}
|
|
20
|
-
/** One synthesized
|
|
20
|
+
/** One synthesized segment of a streaming session, in emission order. */
|
|
21
21
|
export interface TtsAudioChunk {
|
|
22
22
|
index: number;
|
|
23
23
|
text: string;
|
|
@@ -25,10 +25,10 @@ export interface TtsAudioChunk {
|
|
|
25
25
|
sampleRate: number;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* A live streaming-synthesis session. Feed
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* finishes draining the closed input.
|
|
28
|
+
* A live streaming-synthesis session. Feed complete speakable segments with
|
|
29
|
+
* {@link push} (the worker synthesizes each push as-is) and close the input
|
|
30
|
+
* with {@link end}; `chunks` yields each segment's audio as soon as it is
|
|
31
|
+
* ready, then completes once the worker finishes draining the closed input.
|
|
32
32
|
*/
|
|
33
33
|
export interface TtsStreamHandle {
|
|
34
34
|
push(text: string): void;
|
|
@@ -51,11 +51,12 @@ export declare class TtsClient {
|
|
|
51
51
|
onProgress(listener: (event: TtsProgressEvent) => void): () => void;
|
|
52
52
|
synthesize(modelKey: string, text: string, options?: TtsSynthesizeOptions): Promise<TtsAudio | null>;
|
|
53
53
|
/**
|
|
54
|
-
* Open a streaming-synthesis session.
|
|
55
|
-
* returned handle's `push`/`end`; audio is emitted one
|
|
56
|
-
* a time via `chunks`, so playback can begin before the full text is
|
|
57
|
-
* Returns an inert handle (immediately-ended `chunks`) for unknown
|
|
58
|
-
* an already-aborted signal, and fails the iterator if the worker
|
|
54
|
+
* Open a streaming-synthesis session. Complete speakable segments are fed
|
|
55
|
+
* through the returned handle's `push`/`end`; audio is emitted one segment
|
|
56
|
+
* at a time via `chunks`, so playback can begin before the full text is
|
|
57
|
+
* known. Returns an inert handle (immediately-ended `chunks`) for unknown
|
|
58
|
+
* models or an already-aborted signal, and fails the iterator if the worker
|
|
59
|
+
* cannot spawn.
|
|
59
60
|
*/
|
|
60
61
|
synthesizeStream(modelKey: string, options?: TtsStreamOptions): TtsStreamHandle;
|
|
61
62
|
downloadModel(modelKey: string, options?: TtsDownloadOptions): Promise<boolean>;
|
|
@@ -91,5 +91,12 @@ export type TtsWorkerOutbound = {
|
|
|
91
91
|
*/
|
|
92
92
|
export interface TtsTransport {
|
|
93
93
|
send(message: TtsWorkerOutbound): void;
|
|
94
|
+
/**
|
|
95
|
+
* Send and resolve once the message has drained into the IPC channel.
|
|
96
|
+
* Streaming synthesis awaits this per audio chunk: ONNX inference blocks
|
|
97
|
+
* the worker's event loop for seconds at a time, so fire-and-forget sends
|
|
98
|
+
* queue unflushed until the session ends and arrive as one burst.
|
|
99
|
+
*/
|
|
100
|
+
sendAndFlush(message: TtsWorkerOutbound): Promise<void>;
|
|
94
101
|
onMessage(handler: (message: TtsWorkerInbound) => void): () => void;
|
|
95
102
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SpeechEnhancer } from "./speech-enhancer";
|
|
1
2
|
export interface VocalizerPlayer {
|
|
2
3
|
start(sampleRate: number): void;
|
|
3
4
|
write(pcm: Float32Array): void;
|
|
@@ -8,16 +9,22 @@ export interface VocalizerPlayer {
|
|
|
8
9
|
export declare class Vocalizer {
|
|
9
10
|
#private;
|
|
10
11
|
constructor(createPlayer?: () => VocalizerPlayer);
|
|
12
|
+
/** Wire (or drop) the per-session enhanced-rewrite service. */
|
|
13
|
+
setEnhancer(enhancer: SpeechEnhancer | null): void;
|
|
11
14
|
/**
|
|
12
|
-
* Stream a delta of assistant text into the
|
|
13
|
-
* is disabled. The
|
|
14
|
-
*
|
|
15
|
+
* Stream a delta of assistant text into the pipeline. No-op when
|
|
16
|
+
* vocalization is disabled. The synthesis session (worker, player) is only
|
|
17
|
+
* opened once the first speakable segment exists, so a reply that
|
|
18
|
+
* normalizes to silence (pure code, tables, URLs) costs nothing. The
|
|
19
|
+
* trailing partial is flushed by {@link flush} or the idle timer. The
|
|
20
|
+
* pipeline (enhanced vs mechanical) is latched per utterance.
|
|
15
21
|
*/
|
|
16
22
|
pushDelta(text: string): void;
|
|
17
23
|
/**
|
|
18
|
-
* Close the current input stream (call at message/turn end).
|
|
19
|
-
*
|
|
20
|
-
*
|
|
24
|
+
* Close the current input stream (call at message/turn end). Drains the
|
|
25
|
+
* trailing partial as final segments; in enhanced mode the session ends
|
|
26
|
+
* only after the last in-flight rewrite has pushed, while the next
|
|
27
|
+
* utterance may already be streaming.
|
|
21
28
|
*/
|
|
22
29
|
flush(): void;
|
|
23
30
|
/**
|
|
@@ -26,8 +33,8 @@ export declare class Vocalizer {
|
|
|
26
33
|
*/
|
|
27
34
|
speak(text: string): void;
|
|
28
35
|
/**
|
|
29
|
-
* Interrupt and drop
|
|
30
|
-
*
|
|
36
|
+
* Interrupt and drop every utterance, killing in-flight playback, synthesis,
|
|
37
|
+
* and rewrites (new turn / user message / Esc interrupt). Audio stops at once.
|
|
31
38
|
*/
|
|
32
39
|
clear(): void;
|
|
33
40
|
/** Lower the volume while the user is speaking (push-to-talk), so speech doesn't drown them out. */
|
|
@@ -385,6 +385,8 @@ export declare const ls: {
|
|
|
385
385
|
}): Promise<string[]>;
|
|
386
386
|
/** List untracked files (excludes ignored). */
|
|
387
387
|
untracked(cwd: string, signal?: AbortSignal): Promise<string[]>;
|
|
388
|
+
/** List paths present in a ref, optionally filtered to specific paths. */
|
|
389
|
+
tree(cwd: string, ref: string, files?: readonly string[], signal?: AbortSignal): Promise<string[]>;
|
|
388
390
|
/** List submodule paths (recursive). */
|
|
389
391
|
submodules(cwd: string, signal?: AbortSignal): Promise<string[]>;
|
|
390
392
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.2",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
57
57
|
"@babel/parser": "^7.29.7",
|
|
58
58
|
"@mozilla/readability": "^0.6.0",
|
|
59
|
-
"@oh-my-pi/hashline": "16.3.
|
|
60
|
-
"@oh-my-pi/omp-stats": "16.3.
|
|
61
|
-
"@oh-my-pi/pi-agent-core": "16.3.
|
|
62
|
-
"@oh-my-pi/pi-ai": "16.3.
|
|
63
|
-
"@oh-my-pi/pi-catalog": "16.3.
|
|
64
|
-
"@oh-my-pi/pi-mnemopi": "16.3.
|
|
65
|
-
"@oh-my-pi/pi-natives": "16.3.
|
|
66
|
-
"@oh-my-pi/pi-tui": "16.3.
|
|
67
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
68
|
-
"@oh-my-pi/pi-wire": "16.3.
|
|
69
|
-
"@oh-my-pi/snapcompact": "16.3.
|
|
59
|
+
"@oh-my-pi/hashline": "16.3.2",
|
|
60
|
+
"@oh-my-pi/omp-stats": "16.3.2",
|
|
61
|
+
"@oh-my-pi/pi-agent-core": "16.3.2",
|
|
62
|
+
"@oh-my-pi/pi-ai": "16.3.2",
|
|
63
|
+
"@oh-my-pi/pi-catalog": "16.3.2",
|
|
64
|
+
"@oh-my-pi/pi-mnemopi": "16.3.2",
|
|
65
|
+
"@oh-my-pi/pi-natives": "16.3.2",
|
|
66
|
+
"@oh-my-pi/pi-tui": "16.3.2",
|
|
67
|
+
"@oh-my-pi/pi-utils": "16.3.2",
|
|
68
|
+
"@oh-my-pi/pi-wire": "16.3.2",
|
|
69
|
+
"@oh-my-pi/snapcompact": "16.3.2",
|
|
70
70
|
"@opentelemetry/api": "^1.9.1",
|
|
71
71
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
72
72
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -41,7 +41,7 @@ describe("advisor", () => {
|
|
|
41
41
|
type: "toolCall",
|
|
42
42
|
id: "search-timeout",
|
|
43
43
|
name: "grep",
|
|
44
|
-
arguments: { pattern: "needle",
|
|
44
|
+
arguments: { pattern: "needle", path: "packages/coding-agent/src" },
|
|
45
45
|
},
|
|
46
46
|
],
|
|
47
47
|
timestamp: 1,
|
|
@@ -182,8 +182,8 @@ export const fsFixtures: Record<string, GalleryFixture> = {
|
|
|
182
182
|
glob: {
|
|
183
183
|
label: "Glob",
|
|
184
184
|
// Streaming: glob half-typed, no limit yet.
|
|
185
|
-
streamingArgs: {
|
|
186
|
-
args: {
|
|
185
|
+
streamingArgs: { path: "packages/coding-agent/src/tools/*-render" },
|
|
186
|
+
args: { path: "packages/coding-agent/src/**/*.test.ts", limit: 50 },
|
|
187
187
|
result: {
|
|
188
188
|
content: [
|
|
189
189
|
{
|
|
@@ -9,7 +9,7 @@ export const searchFixtures: Record<string, GalleryFixture> = {
|
|
|
9
9
|
},
|
|
10
10
|
args: {
|
|
11
11
|
pattern: "useState",
|
|
12
|
-
|
|
12
|
+
path: "packages/tui/src",
|
|
13
13
|
},
|
|
14
14
|
result: {
|
|
15
15
|
content: [
|
|
@@ -160,7 +160,7 @@ export const searchFixtures: Record<string, GalleryFixture> = {
|
|
|
160
160
|
},
|
|
161
161
|
args: {
|
|
162
162
|
pat: "useState($A)",
|
|
163
|
-
|
|
163
|
+
path: "packages/tui/src/components",
|
|
164
164
|
},
|
|
165
165
|
result: {
|
|
166
166
|
content: [
|
package/src/cli.ts
CHANGED
|
@@ -173,14 +173,20 @@ async function runWorkerEntrypoint(arg: string | undefined): Promise<boolean> {
|
|
|
173
173
|
* worker is idle, and hard-kills the process on parent `disconnect`.
|
|
174
174
|
*/
|
|
175
175
|
async function runIpcSubprocessWorker<In, Out>(
|
|
176
|
-
start: (transport: {
|
|
176
|
+
start: (transport: {
|
|
177
|
+
send(message: Out): void;
|
|
178
|
+
sendAndFlush(message: Out): Promise<void>;
|
|
179
|
+
onMessage(handler: (message: In) => void): () => void;
|
|
180
|
+
}) => void,
|
|
177
181
|
): Promise<void> {
|
|
178
182
|
const { promise: shuttingDown, resolve: shutdown } = Promise.withResolvers<void>();
|
|
183
|
+
type IpcSend = (this: NodeJS.Process, message: unknown, callback?: (error: Error | null) => void) => boolean;
|
|
184
|
+
// `process.send` only exists when spawned with an IPC channel; the parent
|
|
185
|
+
// always spawns us that way. If it's missing, the parent vanished and
|
|
186
|
+
// there's no one to talk to.
|
|
187
|
+
const ipcSend = (): IpcSend | undefined => (process as NodeJS.Process & { send?: IpcSend }).send;
|
|
179
188
|
const send = (message: Out): void => {
|
|
180
|
-
|
|
181
|
-
// parent always spawns us that way. If it's missing, the parent
|
|
182
|
-
// vanished and there's no one to talk to.
|
|
183
|
-
const sender = (process as NodeJS.Process & { send?: (m: unknown) => boolean }).send;
|
|
189
|
+
const sender = ipcSend();
|
|
184
190
|
if (!sender) {
|
|
185
191
|
shutdown();
|
|
186
192
|
return;
|
|
@@ -191,8 +197,24 @@ async function runIpcSubprocessWorker<In, Out>(
|
|
|
191
197
|
shutdown();
|
|
192
198
|
}
|
|
193
199
|
};
|
|
200
|
+
const sendAndFlush = (message: Out): Promise<void> => {
|
|
201
|
+
const sender = ipcSend();
|
|
202
|
+
if (!sender) {
|
|
203
|
+
shutdown();
|
|
204
|
+
return Promise.resolve();
|
|
205
|
+
}
|
|
206
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
207
|
+
try {
|
|
208
|
+
sender.call(process, message, () => resolve());
|
|
209
|
+
} catch {
|
|
210
|
+
shutdown();
|
|
211
|
+
resolve();
|
|
212
|
+
}
|
|
213
|
+
return promise;
|
|
214
|
+
};
|
|
194
215
|
start({
|
|
195
216
|
send,
|
|
217
|
+
sendAndFlush,
|
|
196
218
|
onMessage(handler) {
|
|
197
219
|
const wrap = (data: unknown): void => handler(data as In);
|
|
198
220
|
process.on("message", wrap);
|