@camerontaylor/paseo-plugin 0.11.0-beta.3.fork.1 → 0.11.1-fork.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.
@@ -103,6 +103,11 @@ interface PluginClientContextAliases {
103
103
  addSidebarItem(contribution: PluginSidebarContribution): PluginCleanup;
104
104
  }
105
105
  export interface PluginClientContext extends PluginCommandCapabilities, PluginClientContextAliases {
106
+ /** Play a base64-encoded audio file on this client; resolves when playback ends. */
107
+ playAudio(source: {
108
+ base64: string;
109
+ mimeType: string;
110
+ }): Promise<void>;
106
111
  addSettingsScreen(contribution: PluginSettingsScreenContribution): PluginCleanup;
107
112
  addScreen(contribution: PluginScreenContribution): PluginCleanup;
108
113
  addSidebarHeaderItem(contribution: PluginSidebarItemContribution): PluginCleanup;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { PLUGIN_SDK_PACKAGE_NAMES, pluginSdkEntry } from "./sdk-specifiers.js";
1
2
  export type { PluginTheme, PluginWorkspaceSnapshot, PluginAgentSnapshot, PluginThemeColors, PluginThemeContribution, PluginAttachmentSourceContribution, PluginTimelineData, PluginTimelineItem, PluginTimelineTransformResult, PluginCleanup, } from "./contracts.js";
2
3
  export { defineSettings, settingsRpc, type SettingsDefinition } from "./settings.js";
3
4
  export { defineAttachmentSource, PluginAttachmentItemSchema, PluginAttachmentSearchPayloadSchema, type PluginAttachmentItem, type PluginAttachmentSearchPayload, } from "./attachments.js";
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ // Shared SDK entry. Keep runtime-specific imports and re-exports on /client or /server.
2
+ export { PLUGIN_SDK_PACKAGE_NAMES, pluginSdkEntry } from "./sdk-specifiers.js";
1
3
  export { defineSettings, settingsRpc } from "./settings.js";
2
4
  export { defineAttachmentSource, PluginAttachmentItemSchema, PluginAttachmentSearchPayloadSchema, } from "./attachments.js";
3
5
  export { defineRpc } from "./rpc.js";
@@ -0,0 +1,3 @@
1
+ export declare const PLUGIN_SDK_PACKAGE_NAMES: readonly [string, "@camerontaylor/paseo-plugin", "@camerontaylor/paseo-plugin"];
2
+ export declare function pluginSdkEntry(specifier: string): string | null;
3
+ //# sourceMappingURL=sdk-specifiers.d.ts.map
@@ -0,0 +1,18 @@
1
+ // Preserve the public import identity through fork packaging, which rewrites
2
+ // quoted @getpaseo/* module paths to the fork scope. Both names use host modules.
3
+ export const PLUGIN_SDK_PACKAGE_NAMES = [
4
+ ["@getpaseo", "plugin"].join("/"),
5
+ "@camerontaylor/paseo-plugin",
6
+ // The release transform rewrites this entry to the installed host SDK name.
7
+ "@camerontaylor/paseo-plugin",
8
+ ];
9
+ export function pluginSdkEntry(specifier) {
10
+ for (const name of PLUGIN_SDK_PACKAGE_NAMES) {
11
+ if (specifier === name)
12
+ return "";
13
+ if (specifier.startsWith(`${name}/`))
14
+ return specifier.slice(name.length);
15
+ }
16
+ return null;
17
+ }
18
+ //# sourceMappingURL=sdk-specifiers.js.map
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawnProcess, terminateProcess } from "../process.js";
2
2
  import { Readable, Writable } from "node:stream";
3
3
  import { ClientSideConnection, CLIENT_METHODS, PROTOCOL_VERSION, ndJsonStream, } from "@agentclientprotocol/sdk";
4
4
  import { z } from "zod";
@@ -269,9 +269,9 @@ class AcpRuntime {
269
269
  args: options.options.command.slice(1),
270
270
  env: process.env,
271
271
  };
272
- child = spawn(launch.command, launch.args, {
272
+ child = spawnProcess(launch.command, launch.args, {
273
273
  env: { ...launch.env, ...options.env },
274
- stdio: ["pipe", "pipe", "pipe"],
274
+ stdio: "pipe",
275
275
  });
276
276
  spawnFailure = new Promise((_resolve, reject) => child.once("error", reject));
277
277
  child.stderr.on("data", () => undefined);
@@ -587,10 +587,10 @@ class AcpRuntime {
587
587
  if (child.exitCode !== null || child.signalCode !== null)
588
588
  return;
589
589
  const closed = new Promise((resolve) => child.once("close", () => resolve()));
590
- child.kill("SIGTERM");
590
+ await terminateProcess(child, "SIGTERM");
591
591
  if (await settlesWithin(closed, 1000))
592
592
  return;
593
- child.kill("SIGKILL");
593
+ await terminateProcess(child);
594
594
  if (!(await settlesWithin(closed, 1000))) {
595
595
  throw new Error(`ACP provider ${this.options.options.id} did not terminate after SIGKILL`);
596
596
  }
@@ -1,4 +1,5 @@
1
1
  export type { UsageSourceRegistration, UsageReport, UsageWindow, UsageBalance, UsageDetail, } from "./usage.js";
2
2
  export type { PluginHandlerContext, PluginServerContext, PluginServerContribution, PluginSettings, PluginSettingsState, } from "./contracts.js";
3
3
  export type { PluginHookContext, PluginHookWorkspace, PluginHookAgent, PluginSessionOpenRequest, PluginTurnOutcome, PluginLifecycleEvents, PluginBeforeRequests, PluginLifecycleRegistration, } from "./lifecycle.js";
4
+ export { spawnProcess, execCommand, terminateProcess } from "./process.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export {};
1
+ export { spawnProcess, execCommand, terminateProcess } from "./process.js";
2
2
  //# sourceMappingURL=index.js.map
@@ -68,6 +68,9 @@ export interface PluginLifecycleEvents {
68
68
  "agent.created": {
69
69
  agent: PluginHookAgent;
70
70
  };
71
+ "agent.closed": {
72
+ agent: PluginHookAgent;
73
+ };
71
74
  "workspace.created": {
72
75
  workspace: PluginHookWorkspace;
73
76
  };
@@ -0,0 +1,15 @@
1
+ export declare function isWindowsCommandScript(executablePath: string): boolean;
2
+ /**
3
+ * When spawning with `shell: true` on Windows, the command is passed to
4
+ * `cmd.exe /d /s /c "command args"`. The `/s` strips outer quotes, so a
5
+ * command path with spaces (e.g. `C:\Program Files\...`) is split at the
6
+ * space. Wrapping it in quotes produces the correct `"C:\Program Files\..." args`.
7
+ */
8
+ export declare function quoteWindowsCommand(command: string): string;
9
+ /**
10
+ * `spawn(..., { shell: true })` on Windows also passes argv through `cmd.exe`.
11
+ * Any argument containing spaces must be quoted or it will be split before the
12
+ * child process sees it.
13
+ */
14
+ export declare function quoteWindowsArgument(argument: string): string;
15
+ //# sourceMappingURL=windows-command.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { extname } from "node:path";
2
+ export function isWindowsCommandScript(executablePath) {
3
+ const extension = extname(executablePath).toLowerCase();
4
+ return process.platform === "win32" && (extension === ".cmd" || extension === ".bat");
5
+ }
6
+ function escapeWindowsCmdValue(value) {
7
+ if (process.platform !== "win32")
8
+ return value;
9
+ const isQuoted = value.startsWith('"') && value.endsWith('"');
10
+ const unquoted = isQuoted ? value.slice(1, -1) : value;
11
+ // Do NOT double `%` here. cmd.exe only collapses `%%` → `%` inside batch
12
+ // files; on the command line / `cmd /c "..."` `%%` stays literal, which
13
+ // breaks args like git's `--format=%(refname)` (git treats `%%` as the
14
+ // escape for a literal `%`, so the format atoms become literals).
15
+ const escaped = unquoted.replace(/([&|^<>()!])/g, "^$1");
16
+ if (isQuoted || /[\s"]/u.test(unquoted)) {
17
+ const quoted = escaped
18
+ .replace(/(\\*)"/g, (_match, slashes) => `${slashes}${slashes}\\"`)
19
+ .replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
20
+ return `"${quoted}"`;
21
+ }
22
+ return escaped;
23
+ }
24
+ /**
25
+ * When spawning with `shell: true` on Windows, the command is passed to
26
+ * `cmd.exe /d /s /c "command args"`. The `/s` strips outer quotes, so a
27
+ * command path with spaces (e.g. `C:\Program Files\...`) is split at the
28
+ * space. Wrapping it in quotes produces the correct `"C:\Program Files\..." args`.
29
+ */
30
+ export function quoteWindowsCommand(command) {
31
+ return escapeWindowsCmdValue(command);
32
+ }
33
+ /**
34
+ * `spawn(..., { shell: true })` on Windows also passes argv through `cmd.exe`.
35
+ * Any argument containing spaces must be quoted or it will be split before the
36
+ * child process sees it.
37
+ */
38
+ export function quoteWindowsArgument(argument) {
39
+ return escapeWindowsCmdValue(argument);
40
+ }
41
+ //# sourceMappingURL=windows-command.js.map
@@ -0,0 +1,25 @@
1
+ import { type ChildProcess, type ChildProcessWithoutNullStreams, type SpawnOptions } from "node:child_process";
2
+ export interface ExecCommandOptions {
3
+ env?: NodeJS.ProcessEnv;
4
+ cwd?: string;
5
+ encoding?: BufferEncoding;
6
+ killSignal?: NodeJS.Signals;
7
+ timeout?: number;
8
+ maxBuffer?: number;
9
+ shell?: boolean | string;
10
+ signal?: AbortSignal;
11
+ }
12
+ interface ExecCommandResult {
13
+ stdout: string;
14
+ stderr: string;
15
+ }
16
+ /** Launch a CLI with Windows command-script handling and argv quoting. The caller owns env. */
17
+ export declare function spawnProcess(command: string, args: string[], options: SpawnOptions & {
18
+ stdio: "pipe";
19
+ }): ChildProcessWithoutNullStreams;
20
+ export declare function spawnProcess(command: string, args: string[], options?: SpawnOptions): ChildProcess;
21
+ export declare function execCommand(command: string, args: string[], options?: ExecCommandOptions): Promise<ExecCommandResult>;
22
+ /** Force-stop a CLI, including descendants behind Windows command-script launchers. */
23
+ export declare function terminateProcess(child: ChildProcess, signal?: NodeJS.Signals): Promise<void>;
24
+ export {};
25
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { execFile, spawn, } from "node:child_process";
2
+ import { extname } from "node:path";
3
+ import { promisify } from "node:util";
4
+ import { isWindowsCommandScript, quoteWindowsArgument, quoteWindowsCommand, } from "./process-internal/windows-command.js";
5
+ const execFileAsync = promisify(execFile);
6
+ function hasPathSeparator(value) {
7
+ return value.includes("/") || value.includes("\\");
8
+ }
9
+ function shouldUseWindowsShell(command, requestedShell) {
10
+ if (isWindowsCommandScript(command)) {
11
+ return true;
12
+ }
13
+ if (requestedShell !== undefined) {
14
+ return requestedShell;
15
+ }
16
+ return process.platform === "win32" && !hasPathSeparator(command) && !extname(command);
17
+ }
18
+ export function spawnProcess(command, args, options) {
19
+ const spawnOptions = options ?? {};
20
+ const isWindows = process.platform === "win32";
21
+ const shell = shouldUseWindowsShell(command, spawnOptions.shell);
22
+ const shouldQuoteForShell = isWindows && shell !== false;
23
+ const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
24
+ const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
25
+ return spawn(resolvedCommand, resolvedArgs, {
26
+ ...spawnOptions,
27
+ env: options?.env,
28
+ shell,
29
+ signal: options?.signal,
30
+ windowsHide: true,
31
+ });
32
+ }
33
+ export async function execCommand(command, args, options) {
34
+ const isWindows = process.platform === "win32";
35
+ const shell = shouldUseWindowsShell(command, options?.shell);
36
+ const shouldQuoteForShell = isWindows && shell !== false;
37
+ const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
38
+ const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
39
+ return execFileAsync(resolvedCommand, resolvedArgs, {
40
+ cwd: options?.cwd,
41
+ env: options?.env,
42
+ encoding: options?.encoding ?? "utf8",
43
+ signal: options?.signal,
44
+ killSignal: options?.killSignal,
45
+ timeout: options?.timeout,
46
+ maxBuffer: options?.maxBuffer,
47
+ shell,
48
+ windowsHide: true,
49
+ });
50
+ }
51
+ /** Force-stop a CLI, including descendants behind Windows command-script launchers. */
52
+ export async function terminateProcess(child, signal = "SIGKILL") {
53
+ if (process.platform !== "win32") {
54
+ child.kill(signal);
55
+ return;
56
+ }
57
+ if (child.pid === undefined)
58
+ return;
59
+ try {
60
+ await execCommand("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
61
+ shell: false,
62
+ timeout: 5000,
63
+ });
64
+ }
65
+ catch (error) {
66
+ // The process can finish between its close check and taskkill.
67
+ if (!(error instanceof Error && "code" in error && error.code === 128))
68
+ throw error;
69
+ }
70
+ }
71
+ //# sourceMappingURL=process.js.map
@@ -4,6 +4,8 @@ export declare const PROVIDER_PROTOCOL_VERSION: 1;
4
4
  export declare const PROVIDER_CAPABILITIES: readonly ["prompt.message", "prompt.command", "prompt.image", "prompt.output_schema", "prompt.steer", "session.archive", "session.configure", "session.list", "session.persistence", "session.revert.both", "session.revert.conversation", "session.revert.files", "session.subsession", "session.unarchive", "permission", "permission.tool_policy", "timeline.plugin"];
5
5
  export type ProviderCapability = (typeof PROVIDER_CAPABILITIES)[number];
6
6
  export interface ProviderRegistration {
7
+ /** False rejects nonempty system instructions. Omission preserves the existing provider contract. */
8
+ supportsSystemPrompt?: boolean;
7
9
  /** Equal keys share discovery within this provider. Include effective configuration and execution environment. */
8
10
  getCatalogCacheKey?(options: ProviderCatalogOptions): Promise<string | undefined>;
9
11
  /** Default executable and arguments; the daemon resolves overrides before status/connect. */
@@ -1,4 +1,4 @@
1
- import type { ZodType } from "zod";
1
+ import { z, type ZodType } from "zod";
2
2
  import type { JsonValue } from "@camerontaylor/paseo-protocol/agent-types";
3
3
  export interface UsageWindow {
4
4
  id: string;
@@ -62,16 +62,27 @@ export interface UsageAccount {
62
62
  /** Stable across token rotation; [A-Za-z0-9._-]{1,128}. Never a credential or raw email. */
63
63
  key: string;
64
64
  label?: string;
65
+ /** Harness owning this login, e.g. Codex, OpenCode, Pi or OMP. */
66
+ harness?: string;
65
67
  /** Store locator, opaque to the daemon. */
66
68
  input: JsonValue;
67
69
  }
70
+ export declare const UsageScopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
71
+ kind: z.ZodLiteral<"global">;
72
+ }, z.core.$strip>, z.ZodObject<{
73
+ kind: z.ZodLiteral<"session">;
74
+ provider: z.ZodString;
75
+ model: z.ZodOptional<z.ZodString>;
76
+ env: z.ZodRecord<z.ZodString, z.ZodString>;
77
+ }, z.core.$strip>], "kind">;
78
+ export type UsageScope = z.infer<typeof UsageScopeSchema>;
68
79
  export interface UsageSourceRegistration {
69
80
  id: string;
70
81
  label: string;
71
82
  icon?: string;
72
83
  input: ZodType;
73
- /** Every account whose login exists on this machine. Empty when none. */
74
- discover(): Promise<UsageAccount[]>;
84
+ /** Accounts for this scope only. The same key in any scope identifies the same account. */
85
+ discover(scope: UsageScope): Promise<UsageAccount[]>;
75
86
  /** Re-reads the login store; never writes it. */
76
87
  fetch(input: unknown): Promise<UsageReport>;
77
88
  }
@@ -84,6 +95,29 @@ export declare function windowFromUsedPct(input: {
84
95
  resetsAt?: string | null;
85
96
  tone?: UsageWindow["tone"];
86
97
  }): UsageWindow;
98
+ /**
99
+ * Numeric provider windows have one identity and vocabulary, independent of response slots.
100
+ * Pass null when the provider omits the duration; reset countdowns are not window lengths.
101
+ * Named API fields (weekly, monthly, etc.) use windowFromUsedPct instead.
102
+ */
103
+ export declare function windowFromReportedDuration(input: {
104
+ durationSeconds: number | null;
105
+ /** Stable quota identity and provider name for a model- or feature-scoped limit. */
106
+ scope?: {
107
+ id: string;
108
+ label: string;
109
+ };
110
+ /** Neutral identity and names when the provider does not report a positive duration. */
111
+ unknown: {
112
+ id: string;
113
+ label: string;
114
+ shortLabel: string;
115
+ };
116
+ utilizationPct: number | null | undefined;
117
+ resetsAt?: string | null;
118
+ summary?: boolean;
119
+ tone?: UsageWindow["tone"];
120
+ }): UsageWindow;
87
121
  /**
88
122
  * The tone scale for anything measured against a known limit, windows and balances alike.
89
123
  *
@@ -1,4 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ export const UsageScopeSchema = z.discriminatedUnion("kind", [
4
+ z.object({ kind: z.literal("global") }),
5
+ z.object({
6
+ kind: z.literal("session"),
7
+ provider: z.string(),
8
+ model: z.string().optional(),
9
+ env: z.record(z.string(), z.string()),
10
+ }),
11
+ ]);
2
12
  export function windowFromUsedPct(input) {
3
13
  const usedPct = typeof input.utilizationPct === "number" ? input.utilizationPct : null;
4
14
  const window = {
@@ -16,6 +26,43 @@ export function windowFromUsedPct(input) {
16
26
  window.tone = input.tone;
17
27
  return window;
18
28
  }
29
+ /**
30
+ * Numeric provider windows have one identity and vocabulary, independent of response slots.
31
+ * Pass null when the provider omits the duration; reset countdowns are not window lengths.
32
+ * Named API fields (weekly, monthly, etc.) use windowFromUsedPct instead.
33
+ */
34
+ export function windowFromReportedDuration(input) {
35
+ const duration = input.durationSeconds;
36
+ const name = duration !== null && Number.isFinite(duration) && duration > 0
37
+ ? durationWindowName(duration)
38
+ : input.unknown;
39
+ const scope = input.scope;
40
+ return windowFromUsedPct({
41
+ id: scope ? `${scope.id}:${name.id}` : name.id,
42
+ label: scope ? `${scope.label} · ${name.label}` : name.label,
43
+ shortLabel: scope
44
+ ? `${scope.label}${name.shortLabel ? ` ${name.shortLabel}` : ""}`
45
+ : name.shortLabel,
46
+ utilizationPct: input.utilizationPct,
47
+ resetsAt: input.resetsAt,
48
+ summary: input.summary,
49
+ tone: input.tone,
50
+ });
51
+ }
52
+ function durationWindowName(seconds) {
53
+ if (seconds === 604800)
54
+ return { id: "weekly", label: "Weekly", shortLabel: "wk" };
55
+ const id = seconds === 18000 ? "five_hour" : `${seconds}s`;
56
+ const units = [
57
+ [86400, "day", "d"],
58
+ [3600, "hour", "h"],
59
+ [60, "minute", "m"],
60
+ [1, "second", "s"],
61
+ ];
62
+ const unit = units.find(([size]) => seconds % size === 0) ?? units[units.length - 1];
63
+ const amount = seconds / unit[0];
64
+ return { id, label: `${amount}-${unit[1]}`, shortLabel: `${amount}${unit[2]}` };
65
+ }
19
66
  /**
20
67
  * The tone scale for anything measured against a known limit, windows and balances alike.
21
68
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camerontaylor/paseo-plugin",
3
- "version": "0.11.0-beta.3.fork.1",
3
+ "version": "0.11.1-fork.1",
4
4
  "description": "Paseo plugin SDK package",
5
5
  "files": [
6
6
  "dist",
@@ -70,8 +70,8 @@
70
70
  "use-sync-external-store": "^1.6.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@camerontaylor/paseo-client": "0.11.0-beta.3.fork.1",
74
- "@camerontaylor/paseo-protocol": "0.11.0-beta.3.fork.1",
73
+ "@camerontaylor/paseo-client": "0.11.1-fork.1",
74
+ "@camerontaylor/paseo-protocol": "0.11.1-fork.1",
75
75
  "@types/node": "^20.9.0",
76
76
  "@types/react": "~19.2.0",
77
77
  "react": "19.1.0",
@@ -81,8 +81,8 @@
81
81
  "zod": "^4.4.3"
82
82
  },
83
83
  "peerDependencies": {
84
- "@camerontaylor/paseo-client": "0.11.0-beta.3.fork.1",
85
- "@camerontaylor/paseo-protocol": "0.11.0-beta.3.fork.1",
84
+ "@camerontaylor/paseo-client": "0.11.1-fork.1",
85
+ "@camerontaylor/paseo-protocol": "0.11.1-fork.1",
86
86
  "react": "~19.1.0",
87
87
  "react-native": ">=0.81.5",
88
88
  "zod": "^4.4.3"