@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.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.
- package/CHANGELOG.md +54 -0
- package/README.md +1 -1
- package/dist/cli.js +4659 -4261
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +316 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +118 -755
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +5 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/usage-cli.ts +10 -1
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +209 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +157 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +210 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1892 -12226
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
package/src/hindsight/state.ts
CHANGED
|
@@ -187,6 +187,17 @@ export class HindsightRetainQueue {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/** Rolling hash of messages[0, count) for retention-cache validation (see #lastRetainedPrefixKey). */
|
|
191
|
+
function retentionPrefixKey(messages: HindsightMessage[], count: number): string {
|
|
192
|
+
let key = "";
|
|
193
|
+
for (let i = 0; i < count; i++) {
|
|
194
|
+
const m = messages[i];
|
|
195
|
+
if (m === undefined) break;
|
|
196
|
+
key = Bun.hash(`${key}\u0000${m.role}\u0000${m.content}`).toString(36);
|
|
197
|
+
}
|
|
198
|
+
return key;
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
/** Per-session Hindsight runtime state owned by its AgentSession. */
|
|
191
202
|
export class HindsightSessionState {
|
|
192
203
|
/** Session id used for retain-queue metadata. */
|
|
@@ -202,6 +213,17 @@ export class HindsightSessionState {
|
|
|
202
213
|
session: AgentSession;
|
|
203
214
|
banksSet: Set<string>;
|
|
204
215
|
lastRetainedTurn: number;
|
|
216
|
+
#lastRetainedMessageIndex: number = 0;
|
|
217
|
+
#cachedTranscript: string = "";
|
|
218
|
+
// Rolling hash of ALL messages in [0, #lastRetainedMessageIndex) at cache
|
|
219
|
+
// time. The incremental full-session cache assumes the branch is append-only;
|
|
220
|
+
// a rewind, branch switch, compaction, or in-place edit rewrites the prefix
|
|
221
|
+
// without changing the session id. Re-hashing the current prefix at use time
|
|
222
|
+
// makes the cache self-healing: on ANY prefix change (not just the boundary
|
|
223
|
+
// message) we rebuild the full transcript instead of retaining stale content
|
|
224
|
+
// or silently retaining nothing forever. Hashing is orders of magnitude
|
|
225
|
+
// cheaper than the re-formatting this cache avoids.
|
|
226
|
+
#lastRetainedPrefixKey: string = "";
|
|
205
227
|
hasRecalledForFirstTurn: boolean;
|
|
206
228
|
lastRecallSnippet?: string;
|
|
207
229
|
/** Cached `<mental_models>` block injected into developer instructions. */
|
|
@@ -236,6 +258,9 @@ export class HindsightSessionState {
|
|
|
236
258
|
this.session = options.session;
|
|
237
259
|
this.banksSet = options.banksSet;
|
|
238
260
|
this.lastRetainedTurn = options.lastRetainedTurn ?? 0;
|
|
261
|
+
this.#lastRetainedMessageIndex = 0;
|
|
262
|
+
this.#cachedTranscript = "";
|
|
263
|
+
this.#lastRetainedPrefixKey = "";
|
|
239
264
|
this.hasRecalledForFirstTurn = options.hasRecalledForFirstTurn ?? false;
|
|
240
265
|
this.aliasOf = options.aliasOf;
|
|
241
266
|
this.retainQueue = new HindsightRetainQueue(this);
|
|
@@ -243,12 +268,18 @@ export class HindsightSessionState {
|
|
|
243
268
|
|
|
244
269
|
setSessionId(sessionId: string): void {
|
|
245
270
|
this.sessionId = sessionId;
|
|
271
|
+
this.#lastRetainedMessageIndex = 0;
|
|
272
|
+
this.#cachedTranscript = "";
|
|
273
|
+
this.#lastRetainedPrefixKey = "";
|
|
246
274
|
}
|
|
247
275
|
|
|
248
276
|
resetConversationTracking(): void {
|
|
249
277
|
this.lastRetainedTurn = 0;
|
|
250
278
|
this.hasRecalledForFirstTurn = false;
|
|
251
279
|
this.lastRecallSnippet = undefined;
|
|
280
|
+
this.#lastRetainedMessageIndex = 0;
|
|
281
|
+
this.#cachedTranscript = "";
|
|
282
|
+
this.#lastRetainedPrefixKey = "";
|
|
252
283
|
}
|
|
253
284
|
|
|
254
285
|
enqueueRetain(content: string, context?: string): void {
|
|
@@ -285,21 +316,35 @@ export class HindsightSessionState {
|
|
|
285
316
|
async retainSession(messages: HindsightMessage[]): Promise<void> {
|
|
286
317
|
const retainedAt = new Date();
|
|
287
318
|
const retainFullWindow = this.config.retainMode === "full-session";
|
|
288
|
-
let target: HindsightMessage[];
|
|
289
319
|
let documentId: string;
|
|
320
|
+
let transcript: string;
|
|
321
|
+
let nextCachedTranscript: string | undefined;
|
|
290
322
|
|
|
291
323
|
if (retainFullWindow) {
|
|
292
|
-
target = messages;
|
|
293
324
|
documentId = this.sessionId;
|
|
325
|
+
const boundary = this.#lastRetainedMessageIndex;
|
|
326
|
+
if (boundary > messages.length || retentionPrefixKey(messages, boundary) !== this.#lastRetainedPrefixKey) {
|
|
327
|
+
this.#lastRetainedMessageIndex = 0;
|
|
328
|
+
this.#cachedTranscript = "";
|
|
329
|
+
this.#lastRetainedPrefixKey = "";
|
|
330
|
+
}
|
|
331
|
+
const newMessages = messages.slice(this.#lastRetainedMessageIndex);
|
|
332
|
+
const { transcript: newPart } = prepareRetentionTranscript(newMessages, true);
|
|
333
|
+
if (!newPart) return;
|
|
334
|
+
nextCachedTranscript = this.#cachedTranscript ? `${this.#cachedTranscript}\n\n${newPart}` : newPart;
|
|
335
|
+
transcript = nextCachedTranscript;
|
|
294
336
|
} else {
|
|
295
337
|
const windowTurns = this.config.retainEveryNTurns + this.config.retainOverlapTurns;
|
|
296
|
-
target = sliceLastTurnsByUserBoundary(messages, windowTurns);
|
|
338
|
+
const target = sliceLastTurnsByUserBoundary(messages, windowTurns);
|
|
297
339
|
documentId = `${this.sessionId}-${retainedAt.getTime()}`;
|
|
340
|
+
this.#lastRetainedMessageIndex = 0;
|
|
341
|
+
this.#cachedTranscript = "";
|
|
342
|
+
this.#lastRetainedPrefixKey = "";
|
|
343
|
+
const { transcript: windowTranscript } = prepareRetentionTranscript(target, true);
|
|
344
|
+
if (!windowTranscript) return;
|
|
345
|
+
transcript = windowTranscript;
|
|
298
346
|
}
|
|
299
347
|
|
|
300
|
-
const { transcript } = prepareRetentionTranscript(target, true);
|
|
301
|
-
if (!transcript) return;
|
|
302
|
-
|
|
303
348
|
await ensureBankExists(this.client, this.bankId, this.config, this.banksSet);
|
|
304
349
|
await this.client.retain(this.bankId, transcript, {
|
|
305
350
|
documentId,
|
|
@@ -309,6 +354,11 @@ export class HindsightSessionState {
|
|
|
309
354
|
timestamp: retainedAt,
|
|
310
355
|
async: true,
|
|
311
356
|
});
|
|
357
|
+
if (nextCachedTranscript !== undefined) {
|
|
358
|
+
this.#cachedTranscript = nextCachedTranscript;
|
|
359
|
+
this.#lastRetainedMessageIndex = messages.length;
|
|
360
|
+
this.#lastRetainedPrefixKey = retentionPrefixKey(messages, messages.length);
|
|
361
|
+
}
|
|
312
362
|
}
|
|
313
363
|
|
|
314
364
|
async maybeRetainOnAgentEnd(): Promise<void> {
|
|
@@ -341,6 +391,14 @@ export class HindsightSessionState {
|
|
|
341
391
|
async forceRetainCurrentSession(): Promise<void> {
|
|
342
392
|
const messages = extractMessages(this.session.sessionManager);
|
|
343
393
|
if (messages.length === 0) return;
|
|
394
|
+
// Forced retains are user-initiated rebuilds (`/memory enqueue`): drop the
|
|
395
|
+
// incremental cache so the full transcript is reformatted and resent even
|
|
396
|
+
// when no new messages arrived since the last auto-retain — otherwise a
|
|
397
|
+
// rebuild could never recover an upstream document that was deleted or a
|
|
398
|
+
// previous async retain that never materialized.
|
|
399
|
+
this.#lastRetainedMessageIndex = 0;
|
|
400
|
+
this.#cachedTranscript = "";
|
|
401
|
+
this.#lastRetainedPrefixKey = "";
|
|
344
402
|
try {
|
|
345
403
|
await this.retainSession(messages);
|
|
346
404
|
this.lastRetainedTurn = messages.filter(m => m.role === "user").length;
|
|
@@ -3,6 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
6
|
+
import { isMarkdownPath } from "../utils/lang-from-path";
|
|
6
7
|
import { buildDirectoryResource } from "./filesystem-resource";
|
|
7
8
|
import { parseInternalUrl } from "./parse";
|
|
8
9
|
import { validateRelativePath } from "./skill-protocol";
|
|
@@ -43,8 +44,8 @@ function shortLocalRoot(options: LocalProtocolOptions): string {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function getContentType(filePath: string): InternalResource["contentType"] {
|
|
47
|
+
if (isMarkdownPath(filePath)) return "text/markdown";
|
|
46
48
|
const ext = path.extname(filePath).toLowerCase();
|
|
47
|
-
if (ext === ".md") return "text/markdown";
|
|
48
49
|
if (ext === ".json") return "application/json";
|
|
49
50
|
return "text/plain";
|
|
50
51
|
}
|
|
@@ -4,6 +4,7 @@ import { getAgentDir, isEnoent } from "@oh-my-pi/pi-utils";
|
|
|
4
4
|
import { getMemoryRoot } from "../memories";
|
|
5
5
|
import { getMnemopiSessionState, type MnemopiScopedMemoryHit, type MnemopiSessionState } from "../mnemopi/state";
|
|
6
6
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
7
|
+
import { isMarkdownPath } from "../utils/lang-from-path";
|
|
7
8
|
import { buildDirectoryResource } from "./filesystem-resource";
|
|
8
9
|
import { validateRelativePath } from "./skill-protocol";
|
|
9
10
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
|
|
@@ -117,8 +118,7 @@ async function tryResolveInRoot(url: InternalUrl, memoryRoot: string): Promise<I
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
const content = await Bun.file(realTargetPath).text();
|
|
120
|
-
const
|
|
121
|
-
const contentType: InternalResource["contentType"] = ext === ".md" ? "text/markdown" : "text/plain";
|
|
121
|
+
const contentType: InternalResource["contentType"] = isMarkdownPath(realTargetPath) ? "text/markdown" : "text/plain";
|
|
122
122
|
|
|
123
123
|
return {
|
|
124
124
|
url: url.href,
|
|
@@ -12,12 +12,12 @@ import * as fs from "node:fs/promises";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
14
14
|
import { getActiveSkills } from "../extensibility/skills";
|
|
15
|
+
import { isMarkdownPath } from "../utils/lang-from-path";
|
|
15
16
|
import { buildDirectoryResource } from "./filesystem-resource";
|
|
16
17
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
|
|
17
18
|
|
|
18
19
|
function getContentType(filePath: string): InternalResource["contentType"] {
|
|
19
|
-
|
|
20
|
-
if (ext === ".md") return "text/markdown";
|
|
20
|
+
if (isMarkdownPath(filePath)) return "text/markdown";
|
|
21
21
|
return "text/plain";
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
statRemotePath,
|
|
31
31
|
writeRemoteFile,
|
|
32
32
|
} from "../ssh/file-transfer";
|
|
33
|
+
import { isMarkdownPath } from "../utils/lang-from-path";
|
|
33
34
|
import type {
|
|
34
35
|
InternalResource,
|
|
35
36
|
InternalUrl,
|
|
@@ -44,11 +45,11 @@ const SSH_TEXT_MAX_BYTES = 1024 * 1024;
|
|
|
44
45
|
|
|
45
46
|
/** POSIX-aware content type from the last path segment's extension. */
|
|
46
47
|
function contentTypeFor(remotePath: string): InternalResource["contentType"] {
|
|
48
|
+
if (isMarkdownPath(remotePath)) return "text/markdown";
|
|
47
49
|
const slash = remotePath.lastIndexOf("/");
|
|
48
50
|
const base = slash === -1 ? remotePath : remotePath.slice(slash + 1);
|
|
49
51
|
const dot = base.lastIndexOf(".");
|
|
50
52
|
const ext = dot <= 0 ? "" : base.slice(dot).toLowerCase();
|
|
51
|
-
if (ext === ".md") return "text/markdown";
|
|
52
53
|
if (ext === ".json") return "application/json";
|
|
53
54
|
return "text/plain";
|
|
54
55
|
}
|
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { $which, isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { isSettingsInitialized, settings } from "../config/settings";
|
|
5
5
|
import { getDefault } from "../config/settings-schema";
|
|
6
|
+
import { isMarkdownPath } from "../utils/lang-from-path";
|
|
6
7
|
import { parseInternalUrl } from "./parse";
|
|
7
8
|
import { validateRelativePath } from "./skill-protocol";
|
|
8
9
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, WriteContext } from "./types";
|
|
@@ -114,8 +115,8 @@ function toVaultValidationError(error: unknown): Error {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
function getContentType(filePath: string): ContentType {
|
|
118
|
+
if (isMarkdownPath(filePath)) return "text/markdown";
|
|
117
119
|
const ext = path.extname(filePath).toLowerCase();
|
|
118
|
-
if (ext === ".md") return "text/markdown";
|
|
119
120
|
if (ext === ".json") return "application/json";
|
|
120
121
|
return "text/plain";
|
|
121
122
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class OmpPcmSourceProcessor extends AudioWorkletProcessor {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.frames = [];
|
|
5
|
+
this.frameOffset = 0;
|
|
6
|
+
this.queuedSamples = 0;
|
|
7
|
+
this.muted = false;
|
|
8
|
+
this.maxQueuedSamples = Math.floor(sampleRate * 0.75);
|
|
9
|
+
this.port.onmessage = event => {
|
|
10
|
+
const message = event.data;
|
|
11
|
+
if (message.type === "mute") {
|
|
12
|
+
this.muted = message.muted;
|
|
13
|
+
this.clear();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (message.type !== "audio" || this.muted || !(message.samples instanceof Float32Array)) return;
|
|
17
|
+
let samples = message.samples;
|
|
18
|
+
if (samples.length > this.maxQueuedSamples) samples = samples.slice(samples.length - this.maxQueuedSamples);
|
|
19
|
+
this.frames.push(samples);
|
|
20
|
+
this.queuedSamples += samples.length;
|
|
21
|
+
while (this.queuedSamples > this.maxQueuedSamples && this.frames.length > 1) {
|
|
22
|
+
const stale = this.frames.shift();
|
|
23
|
+
this.queuedSamples -= stale.length - this.frameOffset;
|
|
24
|
+
this.frameOffset = 0;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clear() {
|
|
30
|
+
this.frames.length = 0;
|
|
31
|
+
this.frameOffset = 0;
|
|
32
|
+
this.queuedSamples = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process(_inputs, outputs) {
|
|
36
|
+
const output = outputs[0];
|
|
37
|
+
const channel = output && output[0];
|
|
38
|
+
if (!channel) return true;
|
|
39
|
+
channel.fill(0);
|
|
40
|
+
if (this.muted) return true;
|
|
41
|
+
let outputOffset = 0;
|
|
42
|
+
while (outputOffset < channel.length && this.frames.length > 0) {
|
|
43
|
+
const frame = this.frames[0];
|
|
44
|
+
const available = frame.length - this.frameOffset;
|
|
45
|
+
const count = Math.min(available, channel.length - outputOffset);
|
|
46
|
+
channel.set(frame.subarray(this.frameOffset, this.frameOffset + count), outputOffset);
|
|
47
|
+
outputOffset += count;
|
|
48
|
+
this.frameOffset += count;
|
|
49
|
+
this.queuedSamples -= count;
|
|
50
|
+
if (this.frameOffset === frame.length) {
|
|
51
|
+
this.frames.shift();
|
|
52
|
+
this.frameOffset = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
registerProcessor("omp-pcm-source", OmpPcmSourceProcessor);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const INPUT_SAMPLE_RATE = 16_000;
|
|
3
|
+
const LEVEL_INTERVAL_MS = 50;
|
|
4
|
+
const OPEN_TIMEOUT_MS = 20_000;
|
|
5
|
+
let context;
|
|
6
|
+
let peer;
|
|
7
|
+
let channel;
|
|
8
|
+
let sourceNode;
|
|
9
|
+
let sourceTrack;
|
|
10
|
+
let remoteSource;
|
|
11
|
+
let analyser;
|
|
12
|
+
let levelWaveform;
|
|
13
|
+
let levelTimer;
|
|
14
|
+
let disconnectTimer;
|
|
15
|
+
let workletUrl;
|
|
16
|
+
let closing = false;
|
|
17
|
+
let muted = false;
|
|
18
|
+
let openSettled = false;
|
|
19
|
+
let openResolve;
|
|
20
|
+
let openReject;
|
|
21
|
+
let previousInputSample;
|
|
22
|
+
let inputPhase = 0;
|
|
23
|
+
const openPromise = new Promise((resolve, reject) => {
|
|
24
|
+
openResolve = resolve;
|
|
25
|
+
openReject = reject;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function reportFailure(message) {
|
|
29
|
+
if (closing) return;
|
|
30
|
+
if (!openSettled) {
|
|
31
|
+
openSettled = true;
|
|
32
|
+
openReject(new Error(message));
|
|
33
|
+
}
|
|
34
|
+
Promise.resolve(window.__ompLiveFailure(message)).catch(() => {});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function waitForIceGathering() {
|
|
38
|
+
if (peer.iceGatheringState === "complete") return Promise.resolve();
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
peer.removeEventListener("icegatheringstatechange", onChange);
|
|
42
|
+
reject(new Error("Timed out gathering WebRTC ICE candidates"));
|
|
43
|
+
}, 10_000);
|
|
44
|
+
function onChange() {
|
|
45
|
+
if (peer.iceGatheringState !== "complete") return;
|
|
46
|
+
clearTimeout(timeout);
|
|
47
|
+
peer.removeEventListener("icegatheringstatechange", onChange);
|
|
48
|
+
resolve();
|
|
49
|
+
}
|
|
50
|
+
peer.addEventListener("icegatheringstatechange", onChange);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function decodePcm(base64) {
|
|
55
|
+
const encoded = atob(base64);
|
|
56
|
+
const bytes = new Uint8Array(encoded.length);
|
|
57
|
+
for (let index = 0; index < encoded.length; index++) bytes[index] = encoded.charCodeAt(index);
|
|
58
|
+
const view = new DataView(bytes.buffer);
|
|
59
|
+
const samples = new Float32Array(bytes.length / 4);
|
|
60
|
+
for (let index = 0; index < samples.length; index++) samples[index] = view.getFloat32(index * 4, true);
|
|
61
|
+
return samples;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resample(samples) {
|
|
65
|
+
if (samples.length === 0) return new Float32Array();
|
|
66
|
+
const step = INPUT_SAMPLE_RATE / context.sampleRate;
|
|
67
|
+
const prefix = previousInputSample === undefined ? 0 : 1;
|
|
68
|
+
const input = new Float32Array(samples.length + prefix);
|
|
69
|
+
if (prefix === 1) input[0] = previousInputSample;
|
|
70
|
+
input.set(samples, prefix);
|
|
71
|
+
if (prefix === 0) inputPhase = 0;
|
|
72
|
+
const available = input.length - 1;
|
|
73
|
+
if (available <= 0) {
|
|
74
|
+
previousInputSample = input[0];
|
|
75
|
+
return new Float32Array();
|
|
76
|
+
}
|
|
77
|
+
const outputLength = Math.max(0, Math.ceil((available - inputPhase) / step));
|
|
78
|
+
const output = new Float32Array(outputLength);
|
|
79
|
+
let written = 0;
|
|
80
|
+
while (inputPhase < available) {
|
|
81
|
+
const index = Math.floor(inputPhase);
|
|
82
|
+
const fraction = inputPhase - index;
|
|
83
|
+
output[written++] = input[index] + (input[index + 1] - input[index]) * fraction;
|
|
84
|
+
inputPhase += step;
|
|
85
|
+
}
|
|
86
|
+
inputPhase -= available;
|
|
87
|
+
previousInputSample = input[input.length - 1];
|
|
88
|
+
return written === output.length ? output : output.slice(0, written);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function reportLevel() {
|
|
92
|
+
if (!analyser || !levelWaveform) return;
|
|
93
|
+
analyser.getFloatTimeDomainData(levelWaveform);
|
|
94
|
+
let sum = 0;
|
|
95
|
+
for (const sample of levelWaveform) sum += sample * sample;
|
|
96
|
+
const level = Math.min(1, Math.max(0, Math.sqrt(sum / levelWaveform.length)));
|
|
97
|
+
Promise.resolve(window.__ompLiveOutputLevel(level)).catch(() => {});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function attachRemoteTrack(event) {
|
|
101
|
+
if (closing || event.track.kind !== "audio") return;
|
|
102
|
+
if (remoteSource) remoteSource.disconnect();
|
|
103
|
+
const stream = new MediaStream([event.track]);
|
|
104
|
+
remoteSource = context.createMediaStreamSource(stream);
|
|
105
|
+
analyser = context.createAnalyser();
|
|
106
|
+
analyser.fftSize = 1024;
|
|
107
|
+
levelWaveform = new Float32Array(analyser.fftSize);
|
|
108
|
+
remoteSource.connect(analyser);
|
|
109
|
+
analyser.connect(context.destination);
|
|
110
|
+
clearInterval(levelTimer);
|
|
111
|
+
levelTimer = setInterval(reportLevel, LEVEL_INTERVAL_MS);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function start(workletSource) {
|
|
115
|
+
if (peer) throw new Error("Live browser runtime already started");
|
|
116
|
+
context = new AudioContext({ latencyHint: "interactive" });
|
|
117
|
+
await context.resume();
|
|
118
|
+
workletUrl = URL.createObjectURL(new Blob([workletSource], { type: "text/javascript" }));
|
|
119
|
+
await context.audioWorklet.addModule(workletUrl);
|
|
120
|
+
sourceNode = new AudioWorkletNode(context, "omp-pcm-source", {
|
|
121
|
+
numberOfInputs: 0,
|
|
122
|
+
numberOfOutputs: 1,
|
|
123
|
+
outputChannelCount: [1],
|
|
124
|
+
});
|
|
125
|
+
const destination = context.createMediaStreamDestination();
|
|
126
|
+
sourceNode.connect(destination);
|
|
127
|
+
sourceTrack = destination.stream.getAudioTracks()[0];
|
|
128
|
+
sourceTrack.enabled = !muted;
|
|
129
|
+
|
|
130
|
+
peer = new RTCPeerConnection();
|
|
131
|
+
peer.addTrack(sourceTrack, destination.stream);
|
|
132
|
+
peer.ontrack = attachRemoteTrack;
|
|
133
|
+
peer.onconnectionstatechange = () => {
|
|
134
|
+
clearTimeout(disconnectTimer);
|
|
135
|
+
if (peer.connectionState === "failed" || peer.connectionState === "closed") {
|
|
136
|
+
reportFailure(`WebRTC peer connection ${peer.connectionState}`);
|
|
137
|
+
} else if (peer.connectionState === "disconnected") {
|
|
138
|
+
disconnectTimer = setTimeout(() => {
|
|
139
|
+
if (peer && peer.connectionState === "disconnected") reportFailure("WebRTC peer connection disconnected");
|
|
140
|
+
}, 2_000);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
channel = peer.createDataChannel("oai-events");
|
|
144
|
+
channel.onopen = () => {
|
|
145
|
+
if (openSettled) return;
|
|
146
|
+
openSettled = true;
|
|
147
|
+
openResolve();
|
|
148
|
+
};
|
|
149
|
+
channel.onerror = () => reportFailure("Live data channel failed");
|
|
150
|
+
channel.onclose = () => reportFailure("Live data channel closed unexpectedly");
|
|
151
|
+
channel.onmessage = event => {
|
|
152
|
+
if (typeof event.data !== "string") return;
|
|
153
|
+
Promise.resolve(window.__ompLiveServerEvent(event.data)).catch(() => {});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const offer = await peer.createOffer();
|
|
157
|
+
await peer.setLocalDescription(offer);
|
|
158
|
+
await waitForIceGathering();
|
|
159
|
+
if (!peer.localDescription || !peer.localDescription.sdp) throw new Error("Browser produced an empty WebRTC offer");
|
|
160
|
+
return peer.localDescription.sdp;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function acceptAnswer(sdp) {
|
|
164
|
+
if (!peer) throw new Error("Live browser runtime has not started");
|
|
165
|
+
await peer.setRemoteDescription({ type: "answer", sdp });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function waitForOpen() {
|
|
169
|
+
let timeout;
|
|
170
|
+
try {
|
|
171
|
+
await Promise.race([
|
|
172
|
+
openPromise,
|
|
173
|
+
new Promise((_, reject) => {
|
|
174
|
+
timeout = setTimeout(() => reject(new Error("Timed out waiting for the live data channel to open")), OPEN_TIMEOUT_MS);
|
|
175
|
+
}),
|
|
176
|
+
]);
|
|
177
|
+
} finally {
|
|
178
|
+
clearTimeout(timeout);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function send(payload) {
|
|
183
|
+
if (!channel || channel.readyState !== "open") throw new Error("Live data channel is not open");
|
|
184
|
+
channel.send(payload);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function pushAudio(base64) {
|
|
188
|
+
if (!sourceNode || muted) return;
|
|
189
|
+
const output = resample(decodePcm(base64));
|
|
190
|
+
if (output.length === 0) return;
|
|
191
|
+
sourceNode.port.postMessage({ type: "audio", samples: output }, [output.buffer]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function setMuted(nextMuted) {
|
|
195
|
+
muted = nextMuted;
|
|
196
|
+
previousInputSample = undefined;
|
|
197
|
+
inputPhase = 0;
|
|
198
|
+
if (sourceTrack) sourceTrack.enabled = !muted;
|
|
199
|
+
if (sourceNode) sourceNode.port.postMessage({ type: "mute", muted });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function close() {
|
|
203
|
+
if (closing) return;
|
|
204
|
+
closing = true;
|
|
205
|
+
clearInterval(levelTimer);
|
|
206
|
+
clearTimeout(disconnectTimer);
|
|
207
|
+
if (channel) channel.close();
|
|
208
|
+
if (peer) peer.close();
|
|
209
|
+
if (remoteSource) remoteSource.disconnect();
|
|
210
|
+
if (analyser) analyser.disconnect();
|
|
211
|
+
if (sourceNode) {
|
|
212
|
+
sourceNode.port.postMessage({ type: "mute", muted: true });
|
|
213
|
+
sourceNode.disconnect();
|
|
214
|
+
}
|
|
215
|
+
if (sourceTrack) sourceTrack.stop();
|
|
216
|
+
if (workletUrl) URL.revokeObjectURL(workletUrl);
|
|
217
|
+
if (context && context.state !== "closed") await context.close();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
window.ompCodexLive = { start, acceptAnswer, waitForOpen, send, pushAudio, setMuted, close };
|
|
221
|
+
})();
|