@oh-my-pi/pi-coding-agent 17.1.0 → 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 +24 -0
- package/dist/cli.js +3686 -4013
- package/dist/types/config/settings-schema.d.ts +58 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +10 -2
- package/dist/types/live/protocol.d.ts +1 -1
- package/dist/types/live/transport.d.ts +6 -19
- package/dist/types/live/visualizer.d.ts +8 -11
- 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/message-frame.d.ts +8 -4
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/session/agent-session-types.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +20 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/eval-runner.d.ts +2 -0
- package/dist/types/session/messages.d.ts +2 -0
- package/dist/types/session/session-tools.d.ts +15 -0
- package/dist/types/session/streaming-output.d.ts +8 -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/tiny/title-client.d.ts +10 -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/index.d.ts +2 -0
- package/dist/types/tts/streaming-player.d.ts +10 -43
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/package.json +12 -12
- package/src/cli/args.ts +1 -0
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli.ts +8 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/settings-schema.ts +61 -0
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/exec/bash-executor.ts +1 -0
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +76 -23
- package/src/live/protocol.test.ts +3 -3
- package/src/live/protocol.ts +1 -1
- package/src/live/transport.ts +72 -140
- package/src/live/visualizer.ts +114 -134
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/tool-execution.ts +17 -110
- package/src/modes/controllers/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +82 -5
- package/src/modes/controllers/selector-controller.ts +62 -0
- package/src/modes/interactive-mode.ts +19 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/sdk.ts +5 -0
- package/src/session/agent-session-types.ts +9 -0
- package/src/session/agent-session.ts +56 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/eval-runner.ts +5 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-tools.ts +37 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/slash-commands/builtin-registry.ts +165 -9
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- 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 +4 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/bash-interactive.ts +90 -86
- 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/index.ts +5 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/xdev.ts +54 -26
- package/src/tts/streaming-player.ts +81 -340
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- 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/live/audio-worklet.txt +0 -59
- package/src/live/browser-runtime.txt +0 -221
- 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
|
@@ -60,6 +60,7 @@ interface SessionToolsOptions {
|
|
|
60
60
|
autoApprove?: boolean;
|
|
61
61
|
toolRegistry?: Map<string, AgentTool>;
|
|
62
62
|
createVibeTools?: () => AgentTool[];
|
|
63
|
+
createComputerTool?: () => Promise<AgentTool | null>;
|
|
63
64
|
builtInToolNames?: Iterable<string>;
|
|
64
65
|
presentationPinnedToolNames?: ReadonlySet<string>;
|
|
65
66
|
ensureWriteRegistered?: () => Promise<boolean>;
|
|
@@ -84,6 +85,7 @@ export class SessionTools {
|
|
|
84
85
|
#autoApprove: boolean;
|
|
85
86
|
#toolRegistry: Map<string, AgentTool>;
|
|
86
87
|
#createVibeTools: (() => AgentTool[]) | undefined;
|
|
88
|
+
#createComputerTool: SessionToolsOptions["createComputerTool"];
|
|
87
89
|
#installedVibeToolNames = new Set<string>();
|
|
88
90
|
#builtInToolNames: Set<string>;
|
|
89
91
|
#rpcHostToolNames = new Set<string>();
|
|
@@ -111,6 +113,7 @@ export class SessionTools {
|
|
|
111
113
|
this.#autoApprove = options.autoApprove === true;
|
|
112
114
|
this.#toolRegistry = options.toolRegistry ?? new Map();
|
|
113
115
|
this.#createVibeTools = options.createVibeTools;
|
|
116
|
+
this.#createComputerTool = options.createComputerTool;
|
|
114
117
|
this.#builtInToolNames = new Set(options.builtInToolNames ?? []);
|
|
115
118
|
this.#presentationPinnedToolNames = options.presentationPinnedToolNames;
|
|
116
119
|
this.#ensureWriteRegistered = options.ensureWriteRegistered;
|
|
@@ -696,6 +699,40 @@ export class SessionTools {
|
|
|
696
699
|
await this.applyActiveToolsByName([...new Set(nextActive)]);
|
|
697
700
|
}
|
|
698
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Session-scoped enable/disable for the settings-gated `computer` tool.
|
|
704
|
+
*
|
|
705
|
+
* `createTools` derives the built-in slate once at session start, so a runtime
|
|
706
|
+
* `computer.enabled` override alone never changes the active tools. Enabling
|
|
707
|
+
* builds the tool through the config factory on first use (later toggles reuse
|
|
708
|
+
* the registry entry, so only one desktop controller is ever registered) and
|
|
709
|
+
* activates it; disabling drops it from the active set while keeping the
|
|
710
|
+
* registry entry. Takes effect before the next model call.
|
|
711
|
+
*
|
|
712
|
+
* @returns false when enabling was requested but this session cannot build the
|
|
713
|
+
* tool (e.g. restricted child sessions have no factory).
|
|
714
|
+
*/
|
|
715
|
+
async setComputerToolEnabled(enabled: boolean): Promise<boolean> {
|
|
716
|
+
const active = this.getEnabledToolNames();
|
|
717
|
+
if (!enabled) {
|
|
718
|
+
if (active.includes("computer")) {
|
|
719
|
+
await this.applyActiveToolsByName(active.filter(name => name !== "computer"));
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (!this.#toolRegistry.has("computer")) {
|
|
724
|
+
const tool = await this.#createComputerTool?.();
|
|
725
|
+
if (tool?.name !== "computer") return false;
|
|
726
|
+
const wrapped = this.#wrapRuntimeTool(tool);
|
|
727
|
+
this.#toolRegistry.set(wrapped.name, wrapped);
|
|
728
|
+
this.#builtInToolNames.add(wrapped.name);
|
|
729
|
+
}
|
|
730
|
+
if (!active.includes("computer")) {
|
|
731
|
+
await this.applyActiveToolsByName([...active, "computer"]);
|
|
732
|
+
}
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
|
|
699
736
|
/** Rebuilds the stable base prompt for the current tools and model. */
|
|
700
737
|
async refreshBaseSystemPrompt(): Promise<void> {
|
|
701
738
|
if (this.#host.isDisposed() || !this.#rebuildSystemPrompt) return;
|
|
@@ -756,6 +756,10 @@ export class OutputSink {
|
|
|
756
756
|
// Queue of chunks waiting for the file sink to be created.
|
|
757
757
|
#pendingFileWrites?: string[];
|
|
758
758
|
#fileReady = false;
|
|
759
|
+
/** In-flight sink creation, awaited by finalize/dispose so a fd opened by a late chunk is still released. */
|
|
760
|
+
#fileCreation?: Promise<void>;
|
|
761
|
+
/** Set once the spill file has been closed; guards double-close and post-finalize resurrection. */
|
|
762
|
+
#finalized = false;
|
|
759
763
|
|
|
760
764
|
readonly #artifactPath?: string;
|
|
761
765
|
readonly #artifactId?: string;
|
|
@@ -842,6 +846,7 @@ export class OutputSink {
|
|
|
842
846
|
* synchronously. File sink writes are deferred and serialized internally.
|
|
843
847
|
*/
|
|
844
848
|
push(chunk: string): void {
|
|
849
|
+
if (this.#finalized) return;
|
|
845
850
|
chunk = sanitizeWithOptionalSixelPassthrough(chunk, text => sanitizeText(this.#normalizeCarriageReturns(text)));
|
|
846
851
|
|
|
847
852
|
// Throttled onChunk: coalesce chunks arriving inside the throttle window.
|
|
@@ -1018,7 +1023,7 @@ export class OutputSink {
|
|
|
1018
1023
|
// resolves (typically <2). The cap is enforced on drain.
|
|
1019
1024
|
if (!this.#pendingFileWrites) {
|
|
1020
1025
|
this.#pendingFileWrites = [chunk];
|
|
1021
|
-
|
|
1026
|
+
this.#fileCreation = this.#createFileSink();
|
|
1022
1027
|
} else {
|
|
1023
1028
|
this.#pendingFileWrites.push(chunk);
|
|
1024
1029
|
}
|
|
@@ -1255,10 +1260,7 @@ export class OutputSink {
|
|
|
1255
1260
|
this.#flushPendingChunk();
|
|
1256
1261
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1257
1262
|
|
|
1258
|
-
|
|
1259
|
-
this.#flushArtifactTailIfCapped();
|
|
1260
|
-
await this.#file.sink.end();
|
|
1261
|
-
}
|
|
1263
|
+
await this.#finalizeFile();
|
|
1262
1264
|
|
|
1263
1265
|
// Compose the visible output. With head retention, splice head + marker
|
|
1264
1266
|
// + tail when content was elided. Otherwise return the rolling buffer.
|
|
@@ -1321,6 +1323,51 @@ export class OutputSink {
|
|
|
1321
1323
|
artifactId: this.#file?.artifactId,
|
|
1322
1324
|
};
|
|
1323
1325
|
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Flush any capped artifact tail and close the spill file descriptor,
|
|
1329
|
+
* awaiting an in-flight sink creation so a descriptor opened by a late
|
|
1330
|
+
* chunk is still released. Idempotent via {@link #finalized}: the artifact
|
|
1331
|
+
* is finalized exactly once whether the caller reached {@link dump} or
|
|
1332
|
+
* bailed through {@link dispose}. `#file` is left set so {@link dump} can
|
|
1333
|
+
* still read `artifactId` for its summary.
|
|
1334
|
+
*/
|
|
1335
|
+
async #finalizeFile(): Promise<void> {
|
|
1336
|
+
if (this.#finalized) return;
|
|
1337
|
+
this.#finalized = true;
|
|
1338
|
+
if (this.#fileCreation) {
|
|
1339
|
+
await this.#fileCreation.catch(() => undefined);
|
|
1340
|
+
}
|
|
1341
|
+
const file = this.#file;
|
|
1342
|
+
if (!file) return;
|
|
1343
|
+
// The tail/notice replay writes to the sink and can throw (e.g. a disk
|
|
1344
|
+
// write error). Closing the descriptor MUST still happen — otherwise the
|
|
1345
|
+
// fd leaks and the replay error masks the original tool error that put us
|
|
1346
|
+
// on this path. Both failures are swallowed so dispose() never throws.
|
|
1347
|
+
try {
|
|
1348
|
+
this.#flushArtifactTailIfCapped();
|
|
1349
|
+
} catch {
|
|
1350
|
+
/* ignore */
|
|
1351
|
+
} finally {
|
|
1352
|
+
try {
|
|
1353
|
+
await file.sink.end();
|
|
1354
|
+
} catch {
|
|
1355
|
+
/* ignore */
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Release the artifact spill descriptor on an exit path that skips
|
|
1362
|
+
* {@link dump} — a thrown error or abort. Idempotent and safe in a
|
|
1363
|
+
* `finally`: if {@link dump} already ran this is a no-op, otherwise it
|
|
1364
|
+
* flushes the capped tail and closes the sink so the descriptor is not
|
|
1365
|
+
* leaked until a later unrelated read hits `EMFILE` (issue #6463).
|
|
1366
|
+
*/
|
|
1367
|
+
async dispose(): Promise<void> {
|
|
1368
|
+
this.#clearPendingChunkTimer();
|
|
1369
|
+
await this.#finalizeFile();
|
|
1370
|
+
}
|
|
1324
1371
|
}
|
|
1325
1372
|
|
|
1326
1373
|
// =============================================================================
|
|
@@ -33,6 +33,7 @@ import { theme } from "../modes/theme/theme";
|
|
|
33
33
|
import type { InteractiveModeContext } from "../modes/types";
|
|
34
34
|
import { extractLastCodeBlock, extractLastCommand } from "../modes/utils/copy-targets";
|
|
35
35
|
import type { AgentSession, FreshSessionResult } from "../session/agent-session";
|
|
36
|
+
import type { SessionOAuthAccountList } from "../session/agent-session-types";
|
|
36
37
|
import { COMPACT_MODES, parseCompactArgs } from "../session/compact-modes";
|
|
37
38
|
import { resolveResumableSession } from "../session/session-listing";
|
|
38
39
|
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
@@ -52,6 +53,7 @@ import { createMarketplaceManager } from "./helpers/marketplace-manager";
|
|
|
52
53
|
import { handleMcpAcp } from "./helpers/mcp";
|
|
53
54
|
import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
|
|
54
55
|
import { describeRedeemOutcome, type ResetUsageAccount, toResetUsageAccounts } from "./helpers/reset-usage";
|
|
56
|
+
import { matchSessionPinAccounts, toSessionPinAccounts } from "./helpers/session-pin";
|
|
55
57
|
import { handleSshAcp } from "./helpers/ssh";
|
|
56
58
|
import { launchStatsDashboard, parseStatsDashboardArgs } from "./helpers/stats-dashboard";
|
|
57
59
|
import { handleTodoAcp } from "./helpers/todo";
|
|
@@ -88,6 +90,26 @@ function formatFastModeStatus(session: AgentSession): string {
|
|
|
88
90
|
return session.isFastModeEnabled() ? "on" : "off";
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/** `/computer status` label for the session-effective `computer.enabled` value. */
|
|
94
|
+
function formatComputerUseStatus(session: AgentSession): string {
|
|
95
|
+
return session.settings.get("computer.enabled") ? "on" : "off";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply a session-scoped computer-use toggle: flip the active tool slate first
|
|
100
|
+
* (so a failed enable never leaves a stale settings override), then record the
|
|
101
|
+
* runtime override — never `settings.set`, which would persist to settings.json.
|
|
102
|
+
* Returns the operator feedback line.
|
|
103
|
+
*/
|
|
104
|
+
async function applyComputerUseToggle(session: AgentSession, enable: boolean): Promise<string> {
|
|
105
|
+
const applied = await session.setComputerToolEnabled(enable);
|
|
106
|
+
if (enable && !applied) {
|
|
107
|
+
return "Computer use is unavailable in this session.";
|
|
108
|
+
}
|
|
109
|
+
session.settings.override("computer.enabled", enable);
|
|
110
|
+
return `Computer use ${enable ? "enabled" : "disabled"} for this session.`;
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
const AUTOCOMPLETE_DETAIL_LIMIT = 48;
|
|
92
114
|
|
|
93
115
|
function shortDetail(value: string, limit = AUTOCOMPLETE_DETAIL_LIMIT): string {
|
|
@@ -196,6 +218,74 @@ async function handleUsageResetCommand(
|
|
|
196
218
|
await output(describeRedeemOutcome(outcome, target.label));
|
|
197
219
|
}
|
|
198
220
|
|
|
221
|
+
async function handleSessionPinCommand(
|
|
222
|
+
arg: string,
|
|
223
|
+
session: AgentSession,
|
|
224
|
+
output: SlashCommandRuntime["output"],
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
if (session.isStreaming) {
|
|
227
|
+
await output("Cannot pin an account while the session is streaming.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let accountList: SessionOAuthAccountList | undefined;
|
|
231
|
+
try {
|
|
232
|
+
accountList = await session.listCurrentProviderOAuthAccounts();
|
|
233
|
+
} catch (error) {
|
|
234
|
+
await output(`Could not load provider accounts: ${errorMessage(error)}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (!accountList) {
|
|
238
|
+
await output("Select a model before pinning a provider account.");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const provider = getOAuthProviders().find(candidate => candidate.id === accountList.provider);
|
|
242
|
+
const providerName = provider?.name ?? accountList.provider;
|
|
243
|
+
const accounts = toSessionPinAccounts(accountList.accounts);
|
|
244
|
+
if (accounts.length === 0) {
|
|
245
|
+
const source = session.modelRegistry.authStorage.describeCredentialSource(
|
|
246
|
+
accountList.provider,
|
|
247
|
+
session.sessionId,
|
|
248
|
+
);
|
|
249
|
+
await output(
|
|
250
|
+
source
|
|
251
|
+
? `No stored OAuth accounts for ${providerName}. Current auth comes from ${source}.`
|
|
252
|
+
: `No stored OAuth accounts for ${providerName}. Use /login to add one.`,
|
|
253
|
+
);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const selector = arg.trim();
|
|
258
|
+
if (!selector) {
|
|
259
|
+
const lines = [`OAuth accounts for ${providerName}:`];
|
|
260
|
+
for (const account of accounts) {
|
|
261
|
+
lines.push(`${account.position + 1}. ${account.label}${account.active ? " (active)" : ""}`);
|
|
262
|
+
}
|
|
263
|
+
lines.push("", "Pin one with `/session pin <number|email|account id>`.");
|
|
264
|
+
await output(lines.join("\n"));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const matches = matchSessionPinAccounts(accounts, selector);
|
|
269
|
+
if (matches.length === 0) {
|
|
270
|
+
await output(`No ${providerName} account matches "${selector}".`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (matches.length > 1) {
|
|
274
|
+
await output(
|
|
275
|
+
`"${selector}" matches multiple ${providerName} accounts: ${matches
|
|
276
|
+
.map(account => `${account.position + 1}. ${account.label}`)
|
|
277
|
+
.join(", ")}. Use the account number.`,
|
|
278
|
+
);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const account = matches[0];
|
|
282
|
+
if (!account || !session.pinCurrentProviderOAuthAccount(account.credentialId)) {
|
|
283
|
+
await output(`${account?.label ?? selector} is no longer available to pin.`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await output(`Pinned ${account.label} to this session for ${providerName}.`);
|
|
287
|
+
}
|
|
288
|
+
|
|
199
289
|
/** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
|
|
200
290
|
function parseShakeMode(args: string): ShakeMode | { error: string } {
|
|
201
291
|
const verb = args.trim().toLowerCase();
|
|
@@ -472,6 +562,49 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
472
562
|
runtime.ctx.editor.setText("");
|
|
473
563
|
},
|
|
474
564
|
},
|
|
565
|
+
{
|
|
566
|
+
name: "computer",
|
|
567
|
+
description: "Toggle the native computer-use tool for this session",
|
|
568
|
+
acpDescription: "Toggle computer use",
|
|
569
|
+
acpInputHint: "[on|off|status]",
|
|
570
|
+
subcommands: [
|
|
571
|
+
{ name: "on", description: "Enable computer use for this session" },
|
|
572
|
+
{ name: "off", description: "Disable computer use for this session" },
|
|
573
|
+
{ name: "status", description: "Show computer use status" },
|
|
574
|
+
],
|
|
575
|
+
allowArgs: true,
|
|
576
|
+
getTuiAutocompleteDescription: runtime => `Computer: ${formatComputerUseStatus(runtime.ctx.session)}`,
|
|
577
|
+
handle: async (command, runtime) => {
|
|
578
|
+
const arg = command.args.trim().toLowerCase();
|
|
579
|
+
if (arg === "status") {
|
|
580
|
+
await runtime.output(`Computer use is ${formatComputerUseStatus(runtime.session)}.`);
|
|
581
|
+
return commandConsumed();
|
|
582
|
+
}
|
|
583
|
+
if (!arg || arg === "toggle" || arg === "on" || arg === "off") {
|
|
584
|
+
const enable = arg === "off" ? false : arg === "on" || !runtime.session.settings.get("computer.enabled");
|
|
585
|
+
await runtime.output(await applyComputerUseToggle(runtime.session, enable));
|
|
586
|
+
return commandConsumed();
|
|
587
|
+
}
|
|
588
|
+
return usage("Usage: /computer [on|off|status]", runtime);
|
|
589
|
+
},
|
|
590
|
+
handleTui: async (command, runtime) => {
|
|
591
|
+
const arg = command.args.trim().toLowerCase();
|
|
592
|
+
if (arg === "status") {
|
|
593
|
+
runtime.ctx.showStatus(`Computer use is ${formatComputerUseStatus(runtime.ctx.session)}.`);
|
|
594
|
+
runtime.ctx.editor.setText("");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!arg || arg === "toggle" || arg === "on" || arg === "off") {
|
|
598
|
+
const enable =
|
|
599
|
+
arg === "off" ? false : arg === "on" || !runtime.ctx.session.settings.get("computer.enabled");
|
|
600
|
+
runtime.ctx.showStatus(await applyComputerUseToggle(runtime.ctx.session, enable));
|
|
601
|
+
runtime.ctx.editor.setText("");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
runtime.ctx.showStatus("Usage: /computer [on|off|status]");
|
|
605
|
+
runtime.ctx.editor.setText("");
|
|
606
|
+
},
|
|
607
|
+
},
|
|
475
608
|
{
|
|
476
609
|
name: "prewalk",
|
|
477
610
|
description: "Switch to a fast/cheap model at the next action (works even without --prewalk)",
|
|
@@ -981,15 +1114,21 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
981
1114
|
{
|
|
982
1115
|
name: "session",
|
|
983
1116
|
description: "Session management commands",
|
|
984
|
-
acpDescription: "Show session
|
|
985
|
-
acpInputHint: "info|delete",
|
|
1117
|
+
acpDescription: "Show or configure the current session",
|
|
1118
|
+
acpInputHint: "[info|delete|pin [account]]",
|
|
986
1119
|
subcommands: [
|
|
987
1120
|
{ name: "info", description: "Show session info and stats" },
|
|
988
1121
|
{ name: "delete", description: "Delete current session and return to selector" },
|
|
1122
|
+
{
|
|
1123
|
+
name: "pin",
|
|
1124
|
+
description: "Pin the current provider to a stored OAuth account",
|
|
1125
|
+
usage: "[account]",
|
|
1126
|
+
},
|
|
989
1127
|
],
|
|
990
1128
|
allowArgs: true,
|
|
991
1129
|
handle: async (command, runtime) => {
|
|
992
|
-
|
|
1130
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
1131
|
+
if (!verb || (verb === "info" && !rest)) {
|
|
993
1132
|
await runtime.output(
|
|
994
1133
|
[
|
|
995
1134
|
`Session: ${runtime.session.sessionId}`,
|
|
@@ -999,7 +1138,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
999
1138
|
);
|
|
1000
1139
|
return commandConsumed();
|
|
1001
1140
|
}
|
|
1002
|
-
if (
|
|
1141
|
+
if (verb === "delete" && !rest) {
|
|
1003
1142
|
if (runtime.session.isStreaming) return usage("Cannot delete the session while streaming.", runtime);
|
|
1004
1143
|
const sessionFile = runtime.sessionManager.getSessionFile();
|
|
1005
1144
|
if (!sessionFile) return usage("No session file to delete (in-memory session).", runtime);
|
|
@@ -1018,17 +1157,34 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
1018
1157
|
);
|
|
1019
1158
|
return commandConsumed();
|
|
1020
1159
|
}
|
|
1021
|
-
|
|
1160
|
+
if (verb === "pin") {
|
|
1161
|
+
await handleSessionPinCommand(rest, runtime.session, runtime.output);
|
|
1162
|
+
return commandConsumed();
|
|
1163
|
+
}
|
|
1164
|
+
return usage("Usage: /session [info|delete|pin [account]]", runtime);
|
|
1022
1165
|
},
|
|
1023
1166
|
handleTui: async (command, runtime) => {
|
|
1024
|
-
const
|
|
1025
|
-
if (
|
|
1167
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
1168
|
+
if (verb === "delete" && !rest) {
|
|
1026
1169
|
runtime.ctx.editor.setText("");
|
|
1027
1170
|
await runtime.ctx.handleSessionDeleteCommand();
|
|
1028
1171
|
return;
|
|
1029
1172
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1173
|
+
if (verb === "pin") {
|
|
1174
|
+
if (rest) {
|
|
1175
|
+
await handleSessionPinCommand(rest, runtime.ctx.session, text => runtime.ctx.showStatus(text));
|
|
1176
|
+
refreshStatusLine(runtime.ctx);
|
|
1177
|
+
} else {
|
|
1178
|
+
await runtime.ctx.showSessionPinSelector();
|
|
1179
|
+
}
|
|
1180
|
+
runtime.ctx.editor.setText("");
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (!verb || (verb === "info" && !rest)) {
|
|
1184
|
+
await runtime.ctx.handleSessionCommand();
|
|
1185
|
+
} else {
|
|
1186
|
+
runtime.ctx.showStatus("Usage: /session [info|delete|pin [account]]");
|
|
1187
|
+
}
|
|
1032
1188
|
runtime.ctx.editor.setText("");
|
|
1033
1189
|
},
|
|
1034
1190
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { OAuthAccountSummary } from "../../session/auth-storage";
|
|
2
|
+
import { formatActiveAccountLabel } from "./active-oauth-account";
|
|
3
|
+
|
|
4
|
+
/** Stored OAuth account rendered and matched by `/session pin`. */
|
|
5
|
+
export interface SessionPinAccount extends OAuthAccountSummary {
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Add stable user-facing labels to provider account summaries. */
|
|
10
|
+
export function toSessionPinAccounts(accounts: readonly OAuthAccountSummary[]): SessionPinAccount[] {
|
|
11
|
+
return accounts.map(account => {
|
|
12
|
+
const enterpriseUrl = account.enterpriseUrl?.trim();
|
|
13
|
+
return {
|
|
14
|
+
...account,
|
|
15
|
+
label: (formatActiveAccountLabel(account) ?? enterpriseUrl) || `OAuth credential #${account.credentialId}`,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Match a `/session pin` selector by 1-based position or exact account identity. */
|
|
21
|
+
export function matchSessionPinAccounts(accounts: readonly SessionPinAccount[], selector: string): SessionPinAccount[] {
|
|
22
|
+
const wanted = selector.trim().toLowerCase();
|
|
23
|
+
if (!wanted) return [];
|
|
24
|
+
if (wanted === "active") return accounts.filter(account => account.active);
|
|
25
|
+
|
|
26
|
+
if (/^\d+$/.test(wanted)) {
|
|
27
|
+
const position = Number(wanted) - 1;
|
|
28
|
+
const positioned = accounts.find(account => account.position === position);
|
|
29
|
+
if (positioned) return [positioned];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return accounts.filter(account =>
|
|
33
|
+
[
|
|
34
|
+
account.label,
|
|
35
|
+
account.email,
|
|
36
|
+
account.accountId,
|
|
37
|
+
account.projectId,
|
|
38
|
+
account.enterpriseUrl,
|
|
39
|
+
account.orgId,
|
|
40
|
+
account.orgName,
|
|
41
|
+
`OAuth credential #${account.credentialId}`,
|
|
42
|
+
].some(value => value?.trim().toLowerCase() === wanted),
|
|
43
|
+
);
|
|
44
|
+
}
|
package/src/stt/downloader.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { getTinyModelsCacheDir } from "@oh-my-pi/pi-utils";
|
|
|
4
4
|
import { sttClient } from "./asr-client";
|
|
5
5
|
import type { SttProgressStatus } from "./asr-protocol";
|
|
6
6
|
import { resolveSttModelSpec } from "./models";
|
|
7
|
-
import { ensureRecorder } from "./recorder";
|
|
8
7
|
|
|
9
8
|
export interface DownloadProgress {
|
|
10
9
|
stage: string;
|
|
@@ -129,7 +128,6 @@ export async function downloadSttModel(
|
|
|
129
128
|
// ── Public API ─────────────────────────────────────────────────────
|
|
130
129
|
|
|
131
130
|
export async function ensureSTTDependencies(options?: EnsureOptions): Promise<void> {
|
|
132
|
-
await ensureRecorder(progress => options?.onProgress?.(progress), options?.signal);
|
|
133
131
|
await downloadSttModel(
|
|
134
132
|
resolveSttModelSpec(options?.modelName).key,
|
|
135
133
|
progress => {
|