@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1
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 +78 -0
- package/README.md +1 -1
- package/dist/cli.js +4685 -4614
- 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 +374 -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/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +48 -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 +35 -0
- package/dist/types/live/visualizer.d.ts +30 -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/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/session-account-selector.d.ts +11 -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/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -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 +321 -0
- package/dist/types/session/agent-session.d.ts +135 -753
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- 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 +51 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +24 -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 +158 -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/streaming-output.d.ts +8 -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/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -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/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -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 +7 -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 +10 -29
- 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/utils/tools-manager.d.ts +1 -2
- 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 +3 -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/setup-cli.ts +2 -14
- package/src/cli/usage-cli.ts +10 -1
- package/src/cli.ts +8 -0
- 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 +16 -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 +270 -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/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +90 -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/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- 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/attestation.ts +91 -0
- package/src/live/controller.ts +517 -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 +422 -0
- package/src/live/visualizer.ts +214 -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/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/session-account-selector.ts +62 -0
- 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 +28 -112
- 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/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +255 -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 +80 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +176 -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 +4 -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/computer-safety.md +14 -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/computer.md +26 -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 +215 -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 +343 -0
- package/src/session/agent-session.ts +1940 -12218
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/auth-storage.ts +1 -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 +217 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +266 -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 +980 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +52 -5
- 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 +257 -20
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +45 -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/tiny/title-client.ts +22 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +116 -86
- 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/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +9 -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 +121 -29
- package/src/tts/streaming-player.ts +82 -283
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- 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/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
|
@@ -8,10 +8,11 @@ import type {
|
|
|
8
8
|
AgentToolUpdateCallback,
|
|
9
9
|
ToolLoadMode,
|
|
10
10
|
} from "@oh-my-pi/pi-agent-core";
|
|
11
|
-
import type { ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import type { ComputerSafetyCheck, ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
|
|
12
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
12
13
|
import type { Settings } from "../../config/settings";
|
|
13
14
|
import type { Theme } from "../../modes/theme/theme";
|
|
14
|
-
import { type ApprovalMode, formatApprovalPrompt, resolveApproval } from "../../tools/approval";
|
|
15
|
+
import { type ApprovalMode, formatApprovalPrompt, resolveApproval, truncateForPrompt } from "../../tools/approval";
|
|
15
16
|
import { defaultLoadModeForToolName } from "../../tools/essential-tools";
|
|
16
17
|
import { normalizeToolEventInput, resolveToolEventInput } from "../tool-event-input";
|
|
17
18
|
import { applyToolProxy } from "../tool-proxy";
|
|
@@ -83,6 +84,42 @@ export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: E
|
|
|
83
84
|
return registeredTools.map(rt => wrapRegisteredTool(rt, runner));
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function computerSafetyChecks(context: AgentToolContext | undefined): ComputerSafetyCheck[] {
|
|
88
|
+
const metadata = context?.toolCall?.providerMetadata;
|
|
89
|
+
return metadata?.type === "computer" ? metadata.pendingSafetyChecks : [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function approvalArgs(params: unknown, context: AgentToolContext | undefined): unknown {
|
|
93
|
+
const metadata = context?.toolCall?.providerMetadata;
|
|
94
|
+
return metadata?.type === "computer" ? { actions: metadata.actions } : params;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function toolEventArgs(params: unknown, context: AgentToolContext | undefined): Record<string, unknown> {
|
|
98
|
+
const metadata = context?.toolCall?.providerMetadata;
|
|
99
|
+
if (metadata?.type === "computer") {
|
|
100
|
+
return {
|
|
101
|
+
actions: metadata.actions,
|
|
102
|
+
pendingSafetyChecks: metadata.pendingSafetyChecks,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return params as Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function approvalData(value: string): string {
|
|
109
|
+
const sanitized = sanitizeText(value)
|
|
110
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
111
|
+
.trim();
|
|
112
|
+
const truncated = truncateForPrompt(sanitized, 500);
|
|
113
|
+
return truncated.replace(/([\\`*_{}[\]()<>#+\-.!|])/g, "\\$1");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function safetyCheckLines(checks: readonly ComputerSafetyCheck[]): string[] {
|
|
117
|
+
return checks.map((check, index) => {
|
|
118
|
+
const value = check.message || check.code || check.id;
|
|
119
|
+
return `${index + 1}. ${approvalData(value)}`;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
/**
|
|
87
124
|
* Wraps a tool with extension callbacks for interception.
|
|
88
125
|
* - Emits tool_call event before execution (can block)
|
|
@@ -128,19 +165,25 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
128
165
|
const configuredMode = (settings?.get("tools.approvalMode") ?? "yolo") as ApprovalMode;
|
|
129
166
|
const approvalMode: ApprovalMode = cliAutoApprove ? "yolo" : configuredMode;
|
|
130
167
|
const userPolicies = (settings?.get("tools.approval") ?? {}) as Record<string, unknown>;
|
|
131
|
-
const
|
|
168
|
+
const resolvedArgs = approvalArgs(params, context);
|
|
169
|
+
const resolved = resolveApproval(this.tool, resolvedArgs, approvalMode, userPolicies);
|
|
132
170
|
if (resolved.policy === "deny") {
|
|
133
171
|
throw new Error(
|
|
134
172
|
`Tool "${this.tool.name}" is blocked by user policy.\n` +
|
|
135
173
|
`To allow: remove "tools.approval.${this.tool.name}: deny" from config.`,
|
|
136
174
|
);
|
|
137
175
|
}
|
|
176
|
+
const pendingSafetyChecks = computerSafetyChecks(context);
|
|
138
177
|
// An xd:// device dispatch already cleared the write tool's outer gate at
|
|
139
178
|
// this tool's tier — re-prompting would double-ask for one action. Explicit
|
|
140
179
|
// per-tool "prompt" policies and tool-demanded overrides still prompt.
|
|
180
|
+
// Provider safety checks are stronger: yolo, per-tool allow, and xdev approval
|
|
181
|
+
// never acknowledge them on the user's behalf.
|
|
141
182
|
const explicitPrompt = resolved.override || Object.hasOwn(userPolicies, this.tool.name);
|
|
142
183
|
const approvalCheck = {
|
|
143
|
-
required:
|
|
184
|
+
required:
|
|
185
|
+
pendingSafetyChecks.length > 0 ||
|
|
186
|
+
(resolved.policy === "prompt" && (explicitPrompt || context?.xdevApproved !== true)),
|
|
144
187
|
reason: resolved.reason,
|
|
145
188
|
};
|
|
146
189
|
|
|
@@ -171,10 +214,16 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
171
214
|
});
|
|
172
215
|
};
|
|
173
216
|
|
|
174
|
-
//
|
|
217
|
+
// Provider safety checks fail closed without an interactive prompt. Unlike
|
|
218
|
+
// ordinary tier approval, no setting or yolo mode may bypass this gate.
|
|
175
219
|
if (!this.runner.hasUI()) {
|
|
176
220
|
const reason = "no interactive UI available";
|
|
177
221
|
await resolveApproval(false, reason);
|
|
222
|
+
if (pendingSafetyChecks.length > 0) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Tool "${this.tool.name}" has pending provider safety checks but no interactive UI is available.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
178
227
|
throw new Error(
|
|
179
228
|
`Tool "${this.tool.name}" requires approval but no interactive UI available.\n` +
|
|
180
229
|
`Options:\n` +
|
|
@@ -185,12 +234,14 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
185
234
|
}
|
|
186
235
|
|
|
187
236
|
const uiContext = this.runner.getUIContext();
|
|
237
|
+
const basePrompt = formatApprovalPrompt(this.tool, resolvedArgs, approvalCheck.reason);
|
|
238
|
+
const safetyPrompt =
|
|
239
|
+
pendingSafetyChecks.length > 0
|
|
240
|
+
? `${basePrompt}\nProvider safety checks:\n${safetyCheckLines(pendingSafetyChecks).join("\n")}`
|
|
241
|
+
: basePrompt;
|
|
188
242
|
let choice: string | undefined;
|
|
189
243
|
try {
|
|
190
|
-
choice = await uiContext.select(
|
|
191
|
-
"Approve",
|
|
192
|
-
"Deny",
|
|
193
|
-
]);
|
|
244
|
+
choice = await uiContext.select(safetyPrompt, ["Approve", "Deny"]);
|
|
194
245
|
} catch (err) {
|
|
195
246
|
await resolveApproval(false, err instanceof Error ? err.message : "approval aborted");
|
|
196
247
|
throw err;
|
|
@@ -200,6 +251,10 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
200
251
|
if (!approved) {
|
|
201
252
|
throw new Error(`Tool call denied by user: ${this.tool.name}`);
|
|
202
253
|
}
|
|
254
|
+
if (pendingSafetyChecks.length > 0) {
|
|
255
|
+
if (!context) throw new Error("Provider safety approval context is unavailable");
|
|
256
|
+
context.providerSafetyApproved = true;
|
|
257
|
+
}
|
|
203
258
|
}
|
|
204
259
|
|
|
205
260
|
// 2. Emit tool_call event - extensions can block execution
|
|
@@ -211,7 +266,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
211
266
|
toolCallId,
|
|
212
267
|
input: normalizeToolEventInput(
|
|
213
268
|
this.tool.name,
|
|
214
|
-
resolveToolEventInput(this.tool, params
|
|
269
|
+
resolveToolEventInput(this.tool, toolEventArgs(params, context)),
|
|
215
270
|
),
|
|
216
271
|
})) as ToolCallEventResult | undefined;
|
|
217
272
|
|
|
@@ -228,7 +283,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
228
283
|
}
|
|
229
284
|
|
|
230
285
|
// Execute the actual tool
|
|
231
|
-
let result:
|
|
286
|
+
let result: AgentToolResult<TDetails, TParameters>;
|
|
232
287
|
let executionError: Error | undefined;
|
|
233
288
|
|
|
234
289
|
try {
|
|
@@ -249,7 +304,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
249
304
|
toolCallId,
|
|
250
305
|
input: normalizeToolEventInput(
|
|
251
306
|
this.tool.name,
|
|
252
|
-
resolveToolEventInput(this.tool, params
|
|
307
|
+
resolveToolEventInput(this.tool, toolEventArgs(params, context)),
|
|
253
308
|
),
|
|
254
309
|
content: result.content,
|
|
255
310
|
details: result.details,
|
|
@@ -275,6 +330,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
275
330
|
return {
|
|
276
331
|
content: modifiedContent,
|
|
277
332
|
details: modifiedDetails,
|
|
333
|
+
providerMetadata: result.providerMetadata,
|
|
278
334
|
...(effectiveError ? { isError: true } : {}),
|
|
279
335
|
};
|
|
280
336
|
}
|
|
@@ -17,7 +17,7 @@ import * as fs from "node:fs";
|
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
19
|
import { type AuthCredential, SqliteAuthCredentialStore, type TSchema } from "@oh-my-pi/pi-ai";
|
|
20
|
-
import { Text } from "@oh-my-pi/pi-tui";
|
|
20
|
+
import { getKeybindings, type Keybinding, Text } from "@oh-my-pi/pi-tui";
|
|
21
21
|
import {
|
|
22
22
|
getAgentDbPath,
|
|
23
23
|
getAgentDir,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
parseFrontmatter as parseOmpFrontmatter,
|
|
27
27
|
} from "@oh-my-pi/pi-utils";
|
|
28
28
|
import { getPackageDir as getOmpPackageDir } from "../config";
|
|
29
|
+
import { formatKeyHints } from "../config/keybindings";
|
|
29
30
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
30
31
|
import { type SettingPath, Settings } from "../config/settings";
|
|
31
32
|
import { EditTool } from "../edit";
|
|
@@ -373,6 +374,11 @@ async function executeLegacyBashOperations(
|
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
376
|
|
|
377
|
+
/** Format the active shortcut for legacy extensions that render keybinding hints. */
|
|
378
|
+
export function keyText(action: Keybinding): string {
|
|
379
|
+
return formatKeyHints(getKeybindings().getKeys(action));
|
|
380
|
+
}
|
|
381
|
+
|
|
376
382
|
/** Parse frontmatter using the historical Pi package-root helper. */
|
|
377
383
|
export interface ParsedFrontmatter<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
378
384
|
frontmatter: T;
|
|
@@ -1504,7 +1504,25 @@ function evaluateGraphCommonJs(modulePath: string): unknown {
|
|
|
1504
1504
|
}
|
|
1505
1505
|
}
|
|
1506
1506
|
|
|
1507
|
-
|
|
1507
|
+
/**
|
|
1508
|
+
* Register {@link evaluateGraphCommonJs} as the graph-owned CommonJS require
|
|
1509
|
+
* bridge on `globalThis`, first-wins.
|
|
1510
|
+
*
|
|
1511
|
+
* On source-link installs the `@(scope)/pi-coding-agent` root shim is served
|
|
1512
|
+
* from `src/`, so an extension import can evaluate a second instance of this
|
|
1513
|
+
* module with empty graph state. An unconditional set would let that empty
|
|
1514
|
+
* instance clobber the host bundle's populated bridge and break transitive
|
|
1515
|
+
* CommonJS resolution (#6449); guarding preserves the first (host-owned)
|
|
1516
|
+
* registration. Idempotent: a subsequent call with a value already present is a
|
|
1517
|
+
* no-op.
|
|
1518
|
+
*/
|
|
1519
|
+
export function ensureGraphCommonJsRequireRegistered(): void {
|
|
1520
|
+
if (!Reflect.get(globalThis, COMMONJS_REQUIRE_GLOBAL)) {
|
|
1521
|
+
Reflect.set(globalThis, COMMONJS_REQUIRE_GLOBAL, evaluateGraphCommonJs);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
ensureGraphCommonJsRequireRegistered();
|
|
1508
1526
|
|
|
1509
1527
|
let legacyPiLoadTag = 0;
|
|
1510
1528
|
|
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,91 @@
|
|
|
1
|
+
import { deviceCheckGenerateToken } from "@oh-my-pi/pi-natives";
|
|
2
|
+
|
|
3
|
+
const CHATGPT_BUNDLE_ID = "com.openai.codex";
|
|
4
|
+
const APP_SESSION_ID = crypto.randomUUID();
|
|
5
|
+
|
|
6
|
+
type DeviceCheckResult = {
|
|
7
|
+
supported: boolean;
|
|
8
|
+
tokenBase64?: string | null;
|
|
9
|
+
latencyMs?: number | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function cborHeader(major: number, value: number): Buffer {
|
|
13
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid CBOR length: ${value}`);
|
|
14
|
+
if (value < 24) return Buffer.from([major + value]);
|
|
15
|
+
if (value <= 0xff) return Buffer.from([major + 24, value]);
|
|
16
|
+
if (value <= 0xffff) {
|
|
17
|
+
const output = Buffer.allocUnsafe(3);
|
|
18
|
+
output[0] = major + 25;
|
|
19
|
+
output.writeUInt16BE(value, 1);
|
|
20
|
+
return output;
|
|
21
|
+
}
|
|
22
|
+
if (value <= 0xffff_ffff) {
|
|
23
|
+
const output = Buffer.allocUnsafe(5);
|
|
24
|
+
output[0] = major + 26;
|
|
25
|
+
output.writeUInt32BE(value, 1);
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`CBOR length is too large: ${value}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cborUnsigned(value: number): Buffer {
|
|
32
|
+
return cborHeader(0, value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cborText(value: string): Buffer {
|
|
36
|
+
const text = Buffer.from(value, "utf8");
|
|
37
|
+
return Buffer.concat([cborHeader(96, text.length), text]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cborMap(entries: ReadonlyArray<readonly [Buffer, Buffer]>): Buffer {
|
|
41
|
+
const values: Buffer[] = [cborHeader(160, entries.length)];
|
|
42
|
+
for (const [key, value] of entries) values.push(key, value);
|
|
43
|
+
return Buffer.concat(values);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function attestationSignals(): Buffer {
|
|
47
|
+
const resolved = Intl.DateTimeFormat().resolvedOptions();
|
|
48
|
+
const locale = (resolved.locale || "unknown").slice(0, 64);
|
|
49
|
+
const timezone = (resolved.timeZone || "unknown").slice(0, 64);
|
|
50
|
+
const preferredLanguages = Buffer.concat([cborHeader(128, 1), cborText(locale)]);
|
|
51
|
+
return cborMap([
|
|
52
|
+
[cborUnsigned(0), cborUnsigned(1)],
|
|
53
|
+
[cborUnsigned(1), preferredLanguages],
|
|
54
|
+
[cborUnsigned(2), cborText(locale)],
|
|
55
|
+
[cborUnsigned(3), cborText(timezone)],
|
|
56
|
+
[cborUnsigned(4), cborUnsigned(0)],
|
|
57
|
+
[cborUnsigned(5), cborUnsigned(1)],
|
|
58
|
+
[cborUnsigned(6), cborText(APP_SESSION_ID.slice(0, 128))],
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildClientAttestation(result: DeviceCheckResult): string {
|
|
63
|
+
const entries: Array<readonly [Buffer, Buffer]> = [];
|
|
64
|
+
if (result.supported && result.tokenBase64) {
|
|
65
|
+
entries.push([cborText("token"), cborText(result.tokenBase64)]);
|
|
66
|
+
} else {
|
|
67
|
+
entries.push([cborText("error_code"), cborUnsigned(result.supported ? 4 : 3)]);
|
|
68
|
+
}
|
|
69
|
+
entries.push([cborText("bundle_id"), cborText(CHATGPT_BUNDLE_ID)]);
|
|
70
|
+
const signals = attestationSignals();
|
|
71
|
+
entries.push([cborText("f"), Buffer.concat([cborHeader(64, signals.length), signals])]);
|
|
72
|
+
if (result.latencyMs !== undefined && result.latencyMs !== null) {
|
|
73
|
+
const latency = Buffer.allocUnsafe(9);
|
|
74
|
+
latency[0] = 0xfb;
|
|
75
|
+
latency.writeDoubleBE(result.latencyMs, 1);
|
|
76
|
+
entries.push([cborText("t"), latency]);
|
|
77
|
+
}
|
|
78
|
+
return `v1.${cborMap(entries).toString("base64url")}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Generates the Codex DeviceCheck attestation envelope sent as `x-oai-attestation` on ChatGPT-OAuth requests. */
|
|
82
|
+
export async function generateCodexAttestation(): Promise<string | undefined> {
|
|
83
|
+
if (process.platform !== "darwin" || process.arch !== "arm64") return undefined;
|
|
84
|
+
let result: DeviceCheckResult;
|
|
85
|
+
try {
|
|
86
|
+
result = await deviceCheckGenerateToken();
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return JSON.stringify({ v: 1, s: 0, t: buildClientAttestation(result) });
|
|
91
|
+
}
|