@aparte/plugin-approval 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aparté
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @aparte/plugin-approval
2
+
3
+ Approval **modes** for [aparté](https://github.com/apartejs/aparte) tool calls — `plan`, `ask`, `auto-edit`,
4
+ `auto` — from a classification of your own tool names, with a `<aparte-approval-mode>` switch for the
5
+ composer's toolbar. It decides; it never executes anything and stores nothing.
6
+
7
+ ```bash
8
+ npm install @aparte/plugin-approval @aparte/core
9
+ ```
10
+
11
+ ```ts
12
+ import { aparteGlobalConfig } from '@aparte/core';
13
+ import { setupApproval } from '@aparte/plugin-approval';
14
+
15
+ const approval = setupApproval({
16
+ classify: {
17
+ read: ['read_file', 'search', /^list_/],
18
+ write: ['write_file', 'edit_file'],
19
+ exec: ['run_command'],
20
+ },
21
+ mode: 'ask',
22
+ }, aparteGlobalConfig); // the config last, like every setup*: it defaults to the global
23
+ approval.setMode('plan'); // or let the person switch it:
24
+ ```
25
+
26
+ ```html
27
+ <aparte-composer-toolbar>
28
+ <aparte-approval-mode></aparte-approval-mode>
29
+ </aparte-composer-toolbar>
30
+ ```
31
+
32
+ **The table** — `plan` runs read-only tools and refuses the rest with a sentence the model reads
33
+ (so it describes what it would do instead); `ask` asks before every write or execution, through the
34
+ same composer panel a `needsApproval` tool uses; `auto-edit` lets writes through and asks before an
35
+ execution; `auto` never asks. A tool in no list keeps its own `needsApproval`.
36
+
37
+ Under the hood it is one core seam: `config.setApprovalPolicy()`, a per-call policy the client's
38
+ approval channel consults before asking. `@aparte/core` is the only **peer dependency**.
39
+
40
+ > ESM-only. Part of the aparté monorepo.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `<aparte-approval-mode>` — the switch between the four modes, for the composer's toolbar.
3
+ *
4
+ * Mounted where the host wants it, like `<aparte-model-selector>`: the composer is light
5
+ * DOM with no registry, so a control is placed by writing its tag inside
6
+ * `<aparte-composer-toolbar>`. It resolves the config it sits in (`resolveConfig`) and the
7
+ * controller `setupApproval` installed there — with none, it renders disabled and says
8
+ * so once in the console, the way core does for a missing presenter: an affordance that
9
+ * cannot act is not offered.
10
+ */
11
+ import { type AparteConfig, type AparteConfigAware } from '@aparte/core';
12
+ import { type ApprovalMode } from './policy.js';
13
+ /**
14
+ * A select over the four approval modes — plan, ask, auto-edit, auto — bound to the
15
+ * `setupApproval()` of the config this element resolves.
16
+ *
17
+ * @element aparte-approval-mode
18
+ *
19
+ * @example Beside the model selector, in the composer's toolbar
20
+ * ```html
21
+ * <aparte-composer>
22
+ * <aparte-composer-input></aparte-composer-input>
23
+ * <aparte-composer-toolbar>
24
+ * <aparte-approval-mode></aparte-approval-mode>
25
+ * </aparte-composer-toolbar>
26
+ * </aparte-composer>
27
+ * ```
28
+ *
29
+ * @fires aparte-approval-mode-change - After a switch: `{ mode, previousMode }`. Bubbles and crosses shadow roots.
30
+ */
31
+ export declare class AparteApprovalMode extends HTMLElement implements AparteConfigAware {
32
+ private _cfg;
33
+ private _controller;
34
+ private _unsubscribe;
35
+ private _configUnsubscribe;
36
+ private _bound;
37
+ private _labels;
38
+ private readonly _onChange;
39
+ /** The label of each mode, for a localised host. Missing entries keep the English default. */
40
+ get labels(): Partial<Record<ApprovalMode, string>>;
41
+ set labels(next: Partial<Record<ApprovalMode, string>>);
42
+ /** The current mode, or `null` with no setup. */
43
+ get mode(): ApprovalMode | null;
44
+ connectedCallback(): void;
45
+ disconnectedCallback(): void;
46
+ /** See `AparteConfigAware`: a boundary moved, so the controller may be another one. */
47
+ aparteConfigChanged(next: AparteConfig): void;
48
+ private _bind;
49
+ private _render;
50
+ private _syncValue;
51
+ }
52
+ //# sourceMappingURL=aparte-approval-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aparte-approval-mode.d.ts","sourceRoot":"","sources":["../src/aparte-approval-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAEH,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAE5C,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAShE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,SAAQ,WAAY,YAAW,iBAAiB;IAC5E,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA6C;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAWxB;IAEF,8FAA8F;IAC9F,IAAI,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAyB;IAC5E,IAAI,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,EAGrD;IAED,iDAAiD;IACjD,IAAI,IAAI,IAAI,YAAY,GAAG,IAAI,CAAgD;IAE/E,iBAAiB,IAAI,IAAI;IASzB,oBAAoB,IAAI,IAAI;IAS5B,uFAAuF;IACvF,mBAAmB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAI7C,OAAO,CAAC,KAAK;IAgBb,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,UAAU;CAKrB"}
@@ -0,0 +1,79 @@
1
+ import { aparteGlobalConfig } from "@aparte/core";
2
+ const APPROVAL_MODES = ["plan", "ask", "auto-edit", "auto"];
3
+ const matches = (name, m) => typeof m === "string" ? m === name : name.search(m) !== -1;
4
+ function classifyTool(name, classification) {
5
+ if (classification.exec?.some((m) => matches(name, m))) return "exec";
6
+ if (classification.write?.some((m) => matches(name, m))) return "write";
7
+ if (classification.read?.some((m) => matches(name, m))) return "read";
8
+ return void 0;
9
+ }
10
+ const NOUN = { read: "reads", write: "changes files or state", exec: "runs a command" };
11
+ function rulingFor(mode, cls, call) {
12
+ if (mode === "auto") return { verdict: "allow" };
13
+ if (cls === void 0) return void 0;
14
+ if (cls === "read") return { verdict: "allow" };
15
+ if (mode === "plan") {
16
+ return {
17
+ verdict: "deny",
18
+ reason: `Plan mode: \`${call.name}\` ${NOUN[cls]}, and only read-only tools run in this mode. Describe what you would do instead; the user can switch the mode to let you do it.`
19
+ };
20
+ }
21
+ if (mode === "auto-edit") return cls === "write" ? { verdict: "allow" } : { verdict: "ask" };
22
+ return { verdict: "ask" };
23
+ }
24
+ function createApprovalPolicy(getMode, classification) {
25
+ return (call) => rulingFor(getMode(), classifyTool(call.name, classification), call);
26
+ }
27
+ const controllers = /* @__PURE__ */ new WeakMap();
28
+ const installed = /* @__PURE__ */ new WeakMap();
29
+ const isInstalled = (config) => config.getApprovalPolicy() === installed.get(config);
30
+ function setupApproval(options, config = aparteGlobalConfig) {
31
+ controllers.get(config)?.dispose();
32
+ let mode = options.mode ?? "ask";
33
+ const listeners = /* @__PURE__ */ new Set();
34
+ if (options.onModeChange) listeners.add(options.onModeChange);
35
+ const controller = {
36
+ modes: APPROVAL_MODES,
37
+ getMode: () => mode,
38
+ setMode(next) {
39
+ if (!APPROVAL_MODES.includes(next) || next === mode) return;
40
+ const previous = mode;
41
+ mode = next;
42
+ for (const l of listeners) l(next, previous);
43
+ },
44
+ classify: (name) => classifyTool(name, options.classify),
45
+ subscribe(listener) {
46
+ listeners.add(listener);
47
+ return () => {
48
+ listeners.delete(listener);
49
+ };
50
+ },
51
+ dispose() {
52
+ if (controllers.get(config) === controller) {
53
+ if (isInstalled(config)) config.setApprovalPolicy(null);
54
+ controllers.delete(config);
55
+ installed.delete(config);
56
+ }
57
+ listeners.clear();
58
+ }
59
+ };
60
+ const policy = createApprovalPolicy(() => mode, options.classify);
61
+ controllers.set(config, controller);
62
+ installed.set(config, policy);
63
+ config.setApprovalPolicy(policy);
64
+ return controller;
65
+ }
66
+ function getApprovalController(config = aparteGlobalConfig) {
67
+ const controller = controllers.get(config);
68
+ if (!controller) return void 0;
69
+ return isInstalled(config) ? controller : void 0;
70
+ }
71
+ export {
72
+ APPROVAL_MODES as A,
73
+ createApprovalPolicy as a,
74
+ classifyTool as c,
75
+ getApprovalController as g,
76
+ rulingFor as r,
77
+ setupApproval as s
78
+ };
79
+ //# sourceMappingURL=approval-B7URDqOS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-B7URDqOS.js","sources":["../src/policy.ts","../src/approval.ts"],"sourcesContent":["/**\n * The modes, the classes, and the table that joins them.\n *\n * A MODE is what the person switches (\"plan\", \"auto\"); a CLASS is what a tool does\n * (`read`, `write`, `exec`), declared by the host on ITS tool names — the names are\n * wire format the host owns, and no library can know that `run_command` executes and\n * `search_docs` reads. The table below is the whole policy: nine cells, no\n * configuration, so a consumer can read it in one glance and predict every ruling.\n *\n * Nothing here executes anything, remembers anything, or draws anything: it is a pure\n * function from (mode, class, call) to a ruling core already knows how to honour.\n */\nimport type { AparteApprovalPolicy, AparteApprovalRuling, AparteToolCall } from '@aparte/core';\n\n/**\n * `plan` runs read-only tools and refuses the rest with a sentence the model reads;\n * `ask` asks before every write or execution; `auto-edit` lets writes through and asks\n * before an execution; `auto` never asks.\n */\nexport type ApprovalMode = 'plan' | 'ask' | 'auto-edit' | 'auto';\n\n/** The four modes, in the order a switch presents them. */\nexport const APPROVAL_MODES: readonly ApprovalMode[] = ['plan', 'ask', 'auto-edit', 'auto'];\n\n/** What a tool does — the axis the modes decide on. */\nexport type ToolClass = 'read' | 'write' | 'exec';\n\n/** A tool name, exactly, or a pattern over names. */\nexport type ToolMatcher = string | RegExp;\n\n/**\n * The host's tool names, by what they do. A name in no list is UNCLASSIFIED: its\n * own `needsApproval` decides (and `auto` still lets it through — auto is auto).\n */\nexport interface ApprovalClassification {\n read?: readonly ToolMatcher[];\n write?: readonly ToolMatcher[];\n exec?: readonly ToolMatcher[];\n}\n\n// `search`, not `test`: `test` advances `lastIndex` on a `g` or `y` regex the caller\n// owns, so the same name would classify differently on alternate calls — and the gate\n// predicate and the answering channel evaluate the policy one after the other.\nconst matches = (name: string, m: ToolMatcher): boolean =>\n typeof m === 'string' ? m === name : name.search(m) !== -1;\n\n/** The class of a tool name under a classification, or `undefined` when it is in no list. */\nexport function classifyTool(name: string, classification: ApprovalClassification): ToolClass | undefined {\n if (classification.exec?.some((m) => matches(name, m))) return 'exec';\n if (classification.write?.some((m) => matches(name, m))) return 'write';\n if (classification.read?.some((m) => matches(name, m))) return 'read';\n return undefined;\n}\n\nconst NOUN: Record<ToolClass, string> = { read: 'reads', write: 'changes files or state', exec: 'runs a command' };\n\n/**\n * The ruling for one call, from the table. `undefined` means \"no opinion\" — the tool's\n * own `needsApproval` decides, which is what core does when a policy says nothing.\n */\nexport function rulingFor(mode: ApprovalMode, cls: ToolClass | undefined, call: AparteToolCall): AparteApprovalRuling | undefined {\n if (mode === 'auto') return { verdict: 'allow' };\n if (cls === undefined) return undefined;\n if (cls === 'read') return { verdict: 'allow' };\n if (mode === 'plan') {\n return {\n verdict: 'deny',\n reason: `Plan mode: \\`${call.name}\\` ${NOUN[cls]}, and only read-only tools run in this mode. ` +\n 'Describe what you would do instead; the user can switch the mode to let you do it.',\n };\n }\n if (mode === 'auto-edit') return cls === 'write' ? { verdict: 'allow' } : { verdict: 'ask' };\n return { verdict: 'ask' };\n}\n\n/**\n * A core `AparteApprovalPolicy` that reads the CURRENT mode on every call — so a\n * switch flipped mid-run applies to the next tool call, not the next conversation.\n */\nexport function createApprovalPolicy(getMode: () => ApprovalMode, classification: ApprovalClassification): AparteApprovalPolicy {\n return (call) => rulingFor(getMode(), classifyTool(call.name, classification), call);\n}\n","/**\n * `setupApproval` — one call installs a mode-driven approval policy on a config.\n *\n * The plugin owns the MODE (a value the person switches) and the TABLE (policy.ts);\n * core owns the mechanism — `setApprovalPolicy`, the gate, the panel that asks, the\n * refusal the model reads. Nothing is executed here and nothing is stored: a mode\n * survives exactly as long as the page, and a host that wants it remembered reads\n * `getMode()` / `onModeChange` and writes wherever it keeps its preferences.\n */\nimport { aparteGlobalConfig, type AparteConfig, type AparteApprovalPolicy } from '@aparte/core';\nimport {\n APPROVAL_MODES, createApprovalPolicy, classifyTool,\n type ApprovalMode, type ApprovalClassification, type ToolClass,\n} from './policy.js';\n\nexport interface ApprovalSetupOptions {\n /** Your tool names, by what they do. Unlisted names keep their own `needsApproval`. */\n classify: ApprovalClassification;\n /** The starting mode. Default: `ask`. */\n mode?: ApprovalMode;\n /** Called after every switch, with the new and the previous mode. */\n onModeChange?: (mode: ApprovalMode, previous: ApprovalMode) => void;\n}\n\n/** What `setupApproval` returns: the mode, readable and switchable, and the way out. */\nexport interface ApprovalController {\n /** The four modes, in the order a switch presents them. */\n readonly modes: readonly ApprovalMode[];\n getMode(): ApprovalMode;\n /** Switch the mode; the next tool call is ruled under it. Unknown values are ignored. */\n setMode(mode: ApprovalMode): void;\n /** The class this setup gives a tool name, or `undefined`. */\n classify(name: string): ToolClass | undefined;\n /** Be told of every switch — from `setMode` or from a `<aparte-approval-mode>`. Returns the unsubscribe. */\n subscribe(listener: (mode: ApprovalMode, previous: ApprovalMode) => void): () => void;\n /** Remove the policy from the config and forget this controller. */\n dispose(): void;\n}\n\nconst controllers = new WeakMap<AparteConfig, ApprovalController>();\n// The policy each controller installed, so a controller whose policy is gone — a\n// `config.reset()`, a `setApprovalPolicy(null)` from elsewhere — reads as no setup\n// rather than as a switch wired to nothing.\nconst installed = new WeakMap<AparteConfig, AparteApprovalPolicy>();\n/** Whether the policy on `config` is still the one this module installed there. */\nconst isInstalled = (config: AparteConfig): boolean =>\n config.getApprovalPolicy() === installed.get(config);\n\n/**\n * Install the policy on `config` (the global one by default) and return its controller.\n *\n * Calling it twice on one config replaces the first setup — its policy is removed and\n * its listeners dropped — so a hot-reloading host does not stack policies.\n *\n * @example\n * ```ts\n * import { setupApproval } from '@aparte/plugin-approval';\n *\n * const approval = setupApproval({\n * classify: {\n * read: ['read_file', 'search', /^list_/],\n * write: ['write_file', 'edit_file'],\n * exec: ['run_command'],\n * },\n * mode: 'ask',\n * }); // the config last, like every setup*: it defaults to the global\n * approval.setMode('plan'); // or drop <aparte-approval-mode> in the composer's toolbar\n * ```\n */\nexport function setupApproval(options: ApprovalSetupOptions, config: AparteConfig = aparteGlobalConfig): ApprovalController {\n controllers.get(config)?.dispose();\n\n let mode: ApprovalMode = options.mode ?? 'ask';\n const listeners = new Set<(mode: ApprovalMode, previous: ApprovalMode) => void>();\n if (options.onModeChange) listeners.add(options.onModeChange);\n\n const controller: ApprovalController = {\n modes: APPROVAL_MODES,\n getMode: () => mode,\n setMode(next) {\n if (!APPROVAL_MODES.includes(next) || next === mode) return;\n const previous = mode;\n mode = next;\n for (const l of listeners) l(next, previous);\n },\n classify: (name) => classifyTool(name, options.classify),\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n dispose() {\n if (controllers.get(config) === controller) {\n // Only the policy this setup installed: a config whose policy was since\n // replaced by someone else keeps that replacement.\n if (isInstalled(config)) config.setApprovalPolicy(null);\n controllers.delete(config);\n installed.delete(config);\n }\n listeners.clear();\n },\n };\n\n const policy = createApprovalPolicy(() => mode, options.classify);\n // Bookkeeping BEFORE the config is told: `setApprovalPolicy` notifies, and a mounted\n // switch re-resolves its controller on that notify — it must find this one.\n controllers.set(config, controller);\n installed.set(config, policy);\n config.setApprovalPolicy(policy);\n return controller;\n}\n\n/**\n * The controller `setupApproval` installed on a config, or `undefined` — also when\n * its policy has since been removed from the config (a `reset()`, a\n * `setApprovalPolicy(null)`): a switch must not look wired to a policy that is gone.\n * What a `<aparte-approval-mode>` element resolves through the config it sits in.\n */\nexport function getApprovalController(config: AparteConfig = aparteGlobalConfig): ApprovalController | undefined {\n const controller = controllers.get(config);\n if (!controller) return undefined;\n return isInstalled(config) ? controller : undefined;\n}\n"],"names":[],"mappings":";AAsBO,MAAM,iBAA0C,CAAC,QAAQ,OAAO,aAAa,MAAM;AAqB1F,MAAM,UAAU,CAAC,MAAc,MAC3B,OAAO,MAAM,WAAW,MAAM,OAAO,KAAK,OAAO,CAAC,MAAM;AAGrD,SAAS,aAAa,MAAc,gBAA+D;AACtG,MAAI,eAAe,MAAM,KAAK,CAAC,MAAM,QAAQ,MAAM,CAAC,CAAC,EAAG,QAAO;AAC/D,MAAI,eAAe,OAAO,KAAK,CAAC,MAAM,QAAQ,MAAM,CAAC,CAAC,EAAG,QAAO;AAChE,MAAI,eAAe,MAAM,KAAK,CAAC,MAAM,QAAQ,MAAM,CAAC,CAAC,EAAG,QAAO;AAC/D,SAAO;AACX;AAEA,MAAM,OAAkC,EAAE,MAAM,SAAS,OAAO,0BAA0B,MAAM,iBAAA;AAMzF,SAAS,UAAU,MAAoB,KAA4B,MAAwD;AAC9H,MAAI,SAAS,OAAQ,QAAO,EAAE,SAAS,QAAA;AACvC,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI,QAAQ,OAAQ,QAAO,EAAE,SAAS,QAAA;AACtC,MAAI,SAAS,QAAQ;AACjB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,QAAQ,gBAAgB,KAAK,IAAI,MAAM,KAAK,GAAG,CAAC;AAAA,IAAA;AAAA,EAGxD;AACA,MAAI,SAAS,YAAa,QAAO,QAAQ,UAAU,EAAE,SAAS,QAAA,IAAY,EAAE,SAAS,MAAA;AACrF,SAAO,EAAE,SAAS,MAAA;AACtB;AAMO,SAAS,qBAAqB,SAA6B,gBAA8D;AAC5H,SAAO,CAAC,SAAS,UAAU,QAAA,GAAW,aAAa,KAAK,MAAM,cAAc,GAAG,IAAI;AACvF;AC1CA,MAAM,kCAAkB,QAAA;AAIxB,MAAM,gCAAgB,QAAA;AAEtB,MAAM,cAAc,CAAC,WACjB,OAAO,wBAAwB,UAAU,IAAI,MAAM;AAuBhD,SAAS,cAAc,SAA+B,SAAuB,oBAAwC;AACxH,cAAY,IAAI,MAAM,GAAG,QAAA;AAEzB,MAAI,OAAqB,QAAQ,QAAQ;AACzC,QAAM,gCAAgB,IAAA;AACtB,MAAI,QAAQ,aAAc,WAAU,IAAI,QAAQ,YAAY;AAE5D,QAAM,aAAiC;AAAA,IACnC,OAAO;AAAA,IACP,SAAS,MAAM;AAAA,IACf,QAAQ,MAAM;AACV,UAAI,CAAC,eAAe,SAAS,IAAI,KAAK,SAAS,KAAM;AACrD,YAAM,WAAW;AACjB,aAAO;AACP,iBAAW,KAAK,UAAW,GAAE,MAAM,QAAQ;AAAA,IAC/C;AAAA,IACA,UAAU,CAAC,SAAS,aAAa,MAAM,QAAQ,QAAQ;AAAA,IACvD,UAAU,UAAU;AAChB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AAAE,kBAAU,OAAO,QAAQ;AAAA,MAAG;AAAA,IAC/C;AAAA,IACA,UAAU;AACN,UAAI,YAAY,IAAI,MAAM,MAAM,YAAY;AAGxC,YAAI,YAAY,MAAM,EAAG,QAAO,kBAAkB,IAAI;AACtD,oBAAY,OAAO,MAAM;AACzB,kBAAU,OAAO,MAAM;AAAA,MAC3B;AACA,gBAAU,MAAA;AAAA,IACd;AAAA,EAAA;AAGJ,QAAM,SAAS,qBAAqB,MAAM,MAAM,QAAQ,QAAQ;AAGhE,cAAY,IAAI,QAAQ,UAAU;AAClC,YAAU,IAAI,QAAQ,MAAM;AAC5B,SAAO,kBAAkB,MAAM;AAC/B,SAAO;AACX;AAQO,SAAS,sBAAsB,SAAuB,oBAAoD;AAC7G,QAAM,aAAa,YAAY,IAAI,MAAM;AACzC,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,YAAY,MAAM,IAAI,aAAa;AAC9C;"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `setupApproval` — one call installs a mode-driven approval policy on a config.
3
+ *
4
+ * The plugin owns the MODE (a value the person switches) and the TABLE (policy.ts);
5
+ * core owns the mechanism — `setApprovalPolicy`, the gate, the panel that asks, the
6
+ * refusal the model reads. Nothing is executed here and nothing is stored: a mode
7
+ * survives exactly as long as the page, and a host that wants it remembered reads
8
+ * `getMode()` / `onModeChange` and writes wherever it keeps its preferences.
9
+ */
10
+ import { type AparteConfig } from '@aparte/core';
11
+ import { type ApprovalMode, type ApprovalClassification, type ToolClass } from './policy.js';
12
+ export interface ApprovalSetupOptions {
13
+ /** Your tool names, by what they do. Unlisted names keep their own `needsApproval`. */
14
+ classify: ApprovalClassification;
15
+ /** The starting mode. Default: `ask`. */
16
+ mode?: ApprovalMode;
17
+ /** Called after every switch, with the new and the previous mode. */
18
+ onModeChange?: (mode: ApprovalMode, previous: ApprovalMode) => void;
19
+ }
20
+ /** What `setupApproval` returns: the mode, readable and switchable, and the way out. */
21
+ export interface ApprovalController {
22
+ /** The four modes, in the order a switch presents them. */
23
+ readonly modes: readonly ApprovalMode[];
24
+ getMode(): ApprovalMode;
25
+ /** Switch the mode; the next tool call is ruled under it. Unknown values are ignored. */
26
+ setMode(mode: ApprovalMode): void;
27
+ /** The class this setup gives a tool name, or `undefined`. */
28
+ classify(name: string): ToolClass | undefined;
29
+ /** Be told of every switch — from `setMode` or from a `<aparte-approval-mode>`. Returns the unsubscribe. */
30
+ subscribe(listener: (mode: ApprovalMode, previous: ApprovalMode) => void): () => void;
31
+ /** Remove the policy from the config and forget this controller. */
32
+ dispose(): void;
33
+ }
34
+ /**
35
+ * Install the policy on `config` (the global one by default) and return its controller.
36
+ *
37
+ * Calling it twice on one config replaces the first setup — its policy is removed and
38
+ * its listeners dropped — so a hot-reloading host does not stack policies.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { setupApproval } from '@aparte/plugin-approval';
43
+ *
44
+ * const approval = setupApproval({
45
+ * classify: {
46
+ * read: ['read_file', 'search', /^list_/],
47
+ * write: ['write_file', 'edit_file'],
48
+ * exec: ['run_command'],
49
+ * },
50
+ * mode: 'ask',
51
+ * }); // the config last, like every setup*: it defaults to the global
52
+ * approval.setMode('plan'); // or drop <aparte-approval-mode> in the composer's toolbar
53
+ * ```
54
+ */
55
+ export declare function setupApproval(options: ApprovalSetupOptions, config?: AparteConfig): ApprovalController;
56
+ /**
57
+ * The controller `setupApproval` installed on a config, or `undefined` — also when
58
+ * its policy has since been removed from the config (a `reset()`, a
59
+ * `setApprovalPolicy(null)`): a switch must not look wired to a policy that is gone.
60
+ * What a `<aparte-approval-mode>` element resolves through the config it sits in.
61
+ */
62
+ export declare function getApprovalController(config?: AparteConfig): ApprovalController | undefined;
63
+ //# sourceMappingURL=approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAsB,KAAK,YAAY,EAA6B,MAAM,cAAc,CAAC;AAChG,OAAO,EAEH,KAAK,YAAY,EAAE,KAAK,sBAAsB,EAAE,KAAK,SAAS,EACjE,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,oBAAoB;IACjC,uFAAuF;IACvF,QAAQ,EAAE,sBAAsB,CAAC;IACjC,yCAAyC;IACzC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;CACvE;AAED,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,OAAO,IAAI,YAAY,CAAC;IACxB,yFAAyF;IACzF,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9C,4GAA4G;IAC5G,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACtF,oEAAoE;IACpE,OAAO,IAAI,IAAI,CAAC;CACnB;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,GAAE,YAAiC,GAAG,kBAAkB,CAwC1H;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,GAAE,YAAiC,GAAG,kBAAkB,GAAG,SAAS,CAI/G"}
@@ -0,0 +1,664 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/aparte-approval-mode.ts",
8
+ "declarations": [
9
+ {
10
+ "kind": "class",
11
+ "description": "A select over the four approval modes — plan, ask, auto-edit, auto — bound to the\n`setupApproval()` of the config this element resolves.",
12
+ "name": "AparteApprovalMode",
13
+ "members": [
14
+ {
15
+ "kind": "field",
16
+ "name": "_cfg",
17
+ "type": {
18
+ "text": "AparteConfig | null"
19
+ },
20
+ "privacy": "private",
21
+ "default": "null"
22
+ },
23
+ {
24
+ "kind": "field",
25
+ "name": "_controller",
26
+ "type": {
27
+ "text": "ApprovalController | undefined"
28
+ },
29
+ "privacy": "private"
30
+ },
31
+ {
32
+ "kind": "field",
33
+ "name": "_unsubscribe",
34
+ "type": {
35
+ "text": "(() => void) | null"
36
+ },
37
+ "privacy": "private",
38
+ "default": "null"
39
+ },
40
+ {
41
+ "kind": "field",
42
+ "name": "_configUnsubscribe",
43
+ "type": {
44
+ "text": "(() => void) | null"
45
+ },
46
+ "privacy": "private",
47
+ "default": "null"
48
+ },
49
+ {
50
+ "kind": "field",
51
+ "name": "_bound",
52
+ "type": {
53
+ "text": "boolean"
54
+ },
55
+ "privacy": "private",
56
+ "default": "false"
57
+ },
58
+ {
59
+ "kind": "field",
60
+ "name": "_labels",
61
+ "type": {
62
+ "text": "Partial<Record<ApprovalMode, string>>"
63
+ },
64
+ "privacy": "private",
65
+ "default": "{}"
66
+ },
67
+ {
68
+ "kind": "field",
69
+ "name": "_onChange",
70
+ "privacy": "private",
71
+ "readonly": true
72
+ },
73
+ {
74
+ "kind": "field",
75
+ "name": "labels",
76
+ "type": {
77
+ "text": "Partial<Record<ApprovalMode, string>>"
78
+ },
79
+ "description": "The label of each mode, for a localised host. Missing entries keep the English default."
80
+ },
81
+ {
82
+ "kind": "field",
83
+ "name": "mode",
84
+ "type": {
85
+ "text": "ApprovalMode | null"
86
+ },
87
+ "description": "The current mode, or `null` with no setup.",
88
+ "readonly": true
89
+ },
90
+ {
91
+ "kind": "method",
92
+ "name": "aparteConfigChanged",
93
+ "return": {
94
+ "type": {
95
+ "text": "void"
96
+ }
97
+ },
98
+ "parameters": [
99
+ {
100
+ "name": "next",
101
+ "type": {
102
+ "text": "AparteConfig"
103
+ }
104
+ }
105
+ ],
106
+ "description": "See `AparteConfigAware`: a boundary moved, so the controller may be another one."
107
+ },
108
+ {
109
+ "kind": "method",
110
+ "name": "_bind",
111
+ "privacy": "private",
112
+ "return": {
113
+ "type": {
114
+ "text": "void"
115
+ }
116
+ },
117
+ "parameters": [
118
+ {
119
+ "name": "cfg",
120
+ "type": {
121
+ "text": "AparteConfig"
122
+ }
123
+ }
124
+ ]
125
+ },
126
+ {
127
+ "kind": "method",
128
+ "name": "_render",
129
+ "privacy": "private",
130
+ "return": {
131
+ "type": {
132
+ "text": "void"
133
+ }
134
+ }
135
+ },
136
+ {
137
+ "kind": "method",
138
+ "name": "_syncValue",
139
+ "privacy": "private",
140
+ "return": {
141
+ "type": {
142
+ "text": "void"
143
+ }
144
+ }
145
+ }
146
+ ],
147
+ "events": [
148
+ {
149
+ "description": "After a switch: `{ mode, previousMode }`. Bubbles and crosses shadow roots.",
150
+ "name": "aparte-approval-mode-change"
151
+ }
152
+ ],
153
+ "superclass": {
154
+ "name": "HTMLElement"
155
+ },
156
+ "tagName": "aparte-approval-mode",
157
+ "customElement": true,
158
+ "examples": [
159
+ "Beside the model selector, in the composer's toolbar\n```html\n<aparte-composer>\n<aparte-composer-input></aparte-composer-input>\n<aparte-composer-toolbar>\n<aparte-approval-mode></aparte-approval-mode>\n</aparte-composer-toolbar>\n</aparte-composer>\n```"
160
+ ]
161
+ }
162
+ ],
163
+ "exports": [
164
+ {
165
+ "kind": "js",
166
+ "name": "AparteApprovalMode",
167
+ "declaration": {
168
+ "name": "AparteApprovalMode",
169
+ "module": "src/aparte-approval-mode.ts"
170
+ }
171
+ },
172
+ {
173
+ "kind": "custom-element-definition",
174
+ "name": "aparte-approval-mode",
175
+ "declaration": {
176
+ "name": "AparteApprovalMode",
177
+ "module": "src/aparte-approval-mode.ts"
178
+ }
179
+ }
180
+ ]
181
+ },
182
+ {
183
+ "kind": "javascript-module",
184
+ "path": "src/approval.ts",
185
+ "declarations": [
186
+ {
187
+ "kind": "function",
188
+ "name": "setupApproval",
189
+ "return": {
190
+ "type": {
191
+ "text": "ApprovalController"
192
+ }
193
+ },
194
+ "parameters": [
195
+ {
196
+ "name": "options",
197
+ "type": {
198
+ "text": "ApprovalSetupOptions"
199
+ }
200
+ },
201
+ {
202
+ "name": "config",
203
+ "default": "aparteGlobalConfig",
204
+ "type": {
205
+ "text": "AparteConfig"
206
+ }
207
+ }
208
+ ],
209
+ "description": "Install the policy on `config` (the global one by default) and return its controller.\n\nCalling it twice on one config replaces the first setup — its policy is removed and\nits listeners dropped — so a hot-reloading host does not stack policies."
210
+ },
211
+ {
212
+ "kind": "function",
213
+ "name": "getApprovalController",
214
+ "return": {
215
+ "type": {
216
+ "text": "ApprovalController | undefined"
217
+ }
218
+ },
219
+ "parameters": [
220
+ {
221
+ "name": "config",
222
+ "default": "aparteGlobalConfig",
223
+ "type": {
224
+ "text": "AparteConfig"
225
+ }
226
+ }
227
+ ],
228
+ "description": "The controller `setupApproval` installed on a config, or `undefined` — also when\nits policy has since been removed from the config (a `reset()`, a\n`setApprovalPolicy(null)`): a switch must not look wired to a policy that is gone.\nWhat a `<aparte-approval-mode>` element resolves through the config it sits in."
229
+ }
230
+ ],
231
+ "exports": [
232
+ {
233
+ "kind": "js",
234
+ "name": "setupApproval",
235
+ "declaration": {
236
+ "name": "setupApproval",
237
+ "module": "src/approval.ts"
238
+ }
239
+ },
240
+ {
241
+ "kind": "js",
242
+ "name": "getApprovalController",
243
+ "declaration": {
244
+ "name": "getApprovalController",
245
+ "module": "src/approval.ts"
246
+ }
247
+ }
248
+ ]
249
+ },
250
+ {
251
+ "kind": "javascript-module",
252
+ "path": "src/index.node.ts",
253
+ "declarations": [],
254
+ "exports": [
255
+ {
256
+ "kind": "js",
257
+ "name": "setupApproval",
258
+ "declaration": {
259
+ "name": "setupApproval",
260
+ "module": "./approval.js"
261
+ }
262
+ },
263
+ {
264
+ "kind": "js",
265
+ "name": "getApprovalController",
266
+ "declaration": {
267
+ "name": "getApprovalController",
268
+ "module": "./approval.js"
269
+ }
270
+ },
271
+ {
272
+ "kind": "js",
273
+ "name": "ApprovalSetupOptions",
274
+ "declaration": {
275
+ "name": "ApprovalSetupOptions",
276
+ "module": "./approval.js"
277
+ }
278
+ },
279
+ {
280
+ "kind": "js",
281
+ "name": "ApprovalController",
282
+ "declaration": {
283
+ "name": "ApprovalController",
284
+ "module": "./approval.js"
285
+ }
286
+ },
287
+ {
288
+ "kind": "js",
289
+ "name": "APPROVAL_MODES",
290
+ "declaration": {
291
+ "name": "APPROVAL_MODES",
292
+ "module": "./policy.js"
293
+ }
294
+ },
295
+ {
296
+ "kind": "js",
297
+ "name": "classifyTool",
298
+ "declaration": {
299
+ "name": "classifyTool",
300
+ "module": "./policy.js"
301
+ }
302
+ },
303
+ {
304
+ "kind": "js",
305
+ "name": "rulingFor",
306
+ "declaration": {
307
+ "name": "rulingFor",
308
+ "module": "./policy.js"
309
+ }
310
+ },
311
+ {
312
+ "kind": "js",
313
+ "name": "createApprovalPolicy",
314
+ "declaration": {
315
+ "name": "createApprovalPolicy",
316
+ "module": "./policy.js"
317
+ }
318
+ },
319
+ {
320
+ "kind": "js",
321
+ "name": "ApprovalMode",
322
+ "declaration": {
323
+ "name": "ApprovalMode",
324
+ "module": "./policy.js"
325
+ }
326
+ },
327
+ {
328
+ "kind": "js",
329
+ "name": "ToolClass",
330
+ "declaration": {
331
+ "name": "ToolClass",
332
+ "module": "./policy.js"
333
+ }
334
+ },
335
+ {
336
+ "kind": "js",
337
+ "name": "ToolMatcher",
338
+ "declaration": {
339
+ "name": "ToolMatcher",
340
+ "module": "./policy.js"
341
+ }
342
+ },
343
+ {
344
+ "kind": "js",
345
+ "name": "ApprovalClassification",
346
+ "declaration": {
347
+ "name": "ApprovalClassification",
348
+ "module": "./policy.js"
349
+ }
350
+ },
351
+ {
352
+ "kind": "js",
353
+ "name": "AparteApprovalMode",
354
+ "declaration": {
355
+ "name": "AparteApprovalMode",
356
+ "module": "./aparte-approval-mode.js"
357
+ }
358
+ },
359
+ {
360
+ "kind": "js",
361
+ "name": "AparteApprovalPolicy",
362
+ "declaration": {
363
+ "name": "AparteApprovalPolicy",
364
+ "package": "@aparte/core"
365
+ }
366
+ },
367
+ {
368
+ "kind": "js",
369
+ "name": "AparteApprovalRuling",
370
+ "declaration": {
371
+ "name": "AparteApprovalRuling",
372
+ "package": "@aparte/core"
373
+ }
374
+ },
375
+ {
376
+ "kind": "js",
377
+ "name": "AparteToolCall",
378
+ "declaration": {
379
+ "name": "AparteToolCall",
380
+ "package": "@aparte/core"
381
+ }
382
+ },
383
+ {
384
+ "kind": "js",
385
+ "name": "AparteApprovalModeChangeEventDetail",
386
+ "declaration": {
387
+ "name": "AparteApprovalModeChangeEventDetail",
388
+ "package": "@aparte/core"
389
+ }
390
+ }
391
+ ]
392
+ },
393
+ {
394
+ "kind": "javascript-module",
395
+ "path": "src/index.ts",
396
+ "declarations": [],
397
+ "exports": [
398
+ {
399
+ "kind": "js",
400
+ "name": "setupApproval",
401
+ "declaration": {
402
+ "name": "setupApproval",
403
+ "module": "./approval.js"
404
+ }
405
+ },
406
+ {
407
+ "kind": "js",
408
+ "name": "getApprovalController",
409
+ "declaration": {
410
+ "name": "getApprovalController",
411
+ "module": "./approval.js"
412
+ }
413
+ },
414
+ {
415
+ "kind": "js",
416
+ "name": "ApprovalSetupOptions",
417
+ "declaration": {
418
+ "name": "ApprovalSetupOptions",
419
+ "module": "./approval.js"
420
+ }
421
+ },
422
+ {
423
+ "kind": "js",
424
+ "name": "ApprovalController",
425
+ "declaration": {
426
+ "name": "ApprovalController",
427
+ "module": "./approval.js"
428
+ }
429
+ },
430
+ {
431
+ "kind": "js",
432
+ "name": "APPROVAL_MODES",
433
+ "declaration": {
434
+ "name": "APPROVAL_MODES",
435
+ "module": "./policy.js"
436
+ }
437
+ },
438
+ {
439
+ "kind": "js",
440
+ "name": "classifyTool",
441
+ "declaration": {
442
+ "name": "classifyTool",
443
+ "module": "./policy.js"
444
+ }
445
+ },
446
+ {
447
+ "kind": "js",
448
+ "name": "rulingFor",
449
+ "declaration": {
450
+ "name": "rulingFor",
451
+ "module": "./policy.js"
452
+ }
453
+ },
454
+ {
455
+ "kind": "js",
456
+ "name": "createApprovalPolicy",
457
+ "declaration": {
458
+ "name": "createApprovalPolicy",
459
+ "module": "./policy.js"
460
+ }
461
+ },
462
+ {
463
+ "kind": "js",
464
+ "name": "ApprovalMode",
465
+ "declaration": {
466
+ "name": "ApprovalMode",
467
+ "module": "./policy.js"
468
+ }
469
+ },
470
+ {
471
+ "kind": "js",
472
+ "name": "ToolClass",
473
+ "declaration": {
474
+ "name": "ToolClass",
475
+ "module": "./policy.js"
476
+ }
477
+ },
478
+ {
479
+ "kind": "js",
480
+ "name": "ToolMatcher",
481
+ "declaration": {
482
+ "name": "ToolMatcher",
483
+ "module": "./policy.js"
484
+ }
485
+ },
486
+ {
487
+ "kind": "js",
488
+ "name": "ApprovalClassification",
489
+ "declaration": {
490
+ "name": "ApprovalClassification",
491
+ "module": "./policy.js"
492
+ }
493
+ },
494
+ {
495
+ "kind": "js",
496
+ "name": "AparteApprovalMode",
497
+ "declaration": {
498
+ "name": "AparteApprovalMode",
499
+ "module": "./aparte-approval-mode.js"
500
+ }
501
+ },
502
+ {
503
+ "kind": "js",
504
+ "name": "AparteApprovalPolicy",
505
+ "declaration": {
506
+ "name": "AparteApprovalPolicy",
507
+ "package": "@aparte/core"
508
+ }
509
+ },
510
+ {
511
+ "kind": "js",
512
+ "name": "AparteApprovalRuling",
513
+ "declaration": {
514
+ "name": "AparteApprovalRuling",
515
+ "package": "@aparte/core"
516
+ }
517
+ },
518
+ {
519
+ "kind": "js",
520
+ "name": "AparteToolCall",
521
+ "declaration": {
522
+ "name": "AparteToolCall",
523
+ "package": "@aparte/core"
524
+ }
525
+ },
526
+ {
527
+ "kind": "js",
528
+ "name": "AparteApprovalModeChangeEventDetail",
529
+ "declaration": {
530
+ "name": "AparteApprovalModeChangeEventDetail",
531
+ "package": "@aparte/core"
532
+ }
533
+ }
534
+ ]
535
+ },
536
+ {
537
+ "kind": "javascript-module",
538
+ "path": "src/policy.ts",
539
+ "declarations": [
540
+ {
541
+ "kind": "variable",
542
+ "name": "APPROVAL_MODES",
543
+ "type": {
544
+ "text": "readonly ApprovalMode[]"
545
+ },
546
+ "default": "['plan', 'ask', 'auto-edit', 'auto']",
547
+ "description": "The four modes, in the order a switch presents them."
548
+ },
549
+ {
550
+ "kind": "function",
551
+ "name": "classifyTool",
552
+ "return": {
553
+ "type": {
554
+ "text": "ToolClass | undefined"
555
+ }
556
+ },
557
+ "parameters": [
558
+ {
559
+ "name": "name",
560
+ "type": {
561
+ "text": "string"
562
+ }
563
+ },
564
+ {
565
+ "name": "classification",
566
+ "type": {
567
+ "text": "ApprovalClassification"
568
+ }
569
+ }
570
+ ],
571
+ "description": "The class of a tool name under a classification, or `undefined` when it is in no list."
572
+ },
573
+ {
574
+ "kind": "function",
575
+ "name": "rulingFor",
576
+ "return": {
577
+ "type": {
578
+ "text": "AparteApprovalRuling | undefined"
579
+ }
580
+ },
581
+ "parameters": [
582
+ {
583
+ "name": "mode",
584
+ "type": {
585
+ "text": "ApprovalMode"
586
+ }
587
+ },
588
+ {
589
+ "name": "cls",
590
+ "type": {
591
+ "text": "ToolClass | undefined"
592
+ }
593
+ },
594
+ {
595
+ "name": "call",
596
+ "type": {
597
+ "text": "AparteToolCall"
598
+ }
599
+ }
600
+ ],
601
+ "description": "The ruling for one call, from the table. `undefined` means \"no opinion\" — the tool's\nown `needsApproval` decides, which is what core does when a policy says nothing."
602
+ },
603
+ {
604
+ "kind": "function",
605
+ "name": "createApprovalPolicy",
606
+ "return": {
607
+ "type": {
608
+ "text": "AparteApprovalPolicy"
609
+ }
610
+ },
611
+ "parameters": [
612
+ {
613
+ "name": "getMode",
614
+ "type": {
615
+ "text": "() => ApprovalMode"
616
+ }
617
+ },
618
+ {
619
+ "name": "classification",
620
+ "type": {
621
+ "text": "ApprovalClassification"
622
+ }
623
+ }
624
+ ],
625
+ "description": "A core `AparteApprovalPolicy` that reads the CURRENT mode on every call — so a\nswitch flipped mid-run applies to the next tool call, not the next conversation."
626
+ }
627
+ ],
628
+ "exports": [
629
+ {
630
+ "kind": "js",
631
+ "name": "APPROVAL_MODES",
632
+ "declaration": {
633
+ "name": "APPROVAL_MODES",
634
+ "module": "src/policy.ts"
635
+ }
636
+ },
637
+ {
638
+ "kind": "js",
639
+ "name": "classifyTool",
640
+ "declaration": {
641
+ "name": "classifyTool",
642
+ "module": "src/policy.ts"
643
+ }
644
+ },
645
+ {
646
+ "kind": "js",
647
+ "name": "rulingFor",
648
+ "declaration": {
649
+ "name": "rulingFor",
650
+ "module": "src/policy.ts"
651
+ }
652
+ },
653
+ {
654
+ "kind": "js",
655
+ "name": "createApprovalPolicy",
656
+ "declaration": {
657
+ "name": "createApprovalPolicy",
658
+ "module": "src/policy.ts"
659
+ }
660
+ }
661
+ ]
662
+ }
663
+ ]
664
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@aparte/plugin-approval` — approval MODES for tool calls: plan, ask, auto-edit, auto.
3
+ *
4
+ * Core owns the mechanism (a per-call approval policy on the config, the gate in the
5
+ * loop, the panel that asks at the composer, the refusal the model reads); the product
6
+ * owns the policy — which of ITS tool names read, write or execute — and this plugin is
7
+ * the small piece between them: a classification, a switchable mode, a nine-cell table,
8
+ * and a `<aparte-approval-mode>` select for the composer's toolbar. It executes
9
+ * nothing and stores nothing.
10
+ */
11
+ export { setupApproval, getApprovalController } from './approval.js';
12
+ export type { ApprovalSetupOptions, ApprovalController } from './approval.js';
13
+ export { APPROVAL_MODES, classifyTool, rulingFor, createApprovalPolicy } from './policy.js';
14
+ export type { ApprovalMode, ToolClass, ToolMatcher, ApprovalClassification } from './policy.js';
15
+ export { AparteApprovalMode } from './aparte-approval-mode.js';
16
+ export type { AparteApprovalPolicy, AparteApprovalRuling, AparteToolCall, AparteApprovalModeChangeEventDetail, } from '@aparte/core';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACrE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EACR,oBAAoB,EAAE,oBAAoB,EAAE,cAAc,EAAE,mCAAmC,GAClG,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ import { A as APPROVAL_MODES, g as getApprovalController } from "./approval-B7URDqOS.js";
2
+ import { c, a, r, s } from "./approval-B7URDqOS.js";
3
+ import { resolveConfig, subscribeConfigChange, escapeAttr, escapeHtml } from "@aparte/core";
4
+ const DEFAULT_LABELS = {
5
+ plan: "Plan",
6
+ ask: "Ask",
7
+ "auto-edit": "Auto-edit",
8
+ auto: "Auto"
9
+ };
10
+ class AparteApprovalMode extends HTMLElement {
11
+ _cfg = null;
12
+ _controller;
13
+ _unsubscribe = null;
14
+ _configUnsubscribe = null;
15
+ _bound = false;
16
+ _labels = {};
17
+ _onChange = (e) => {
18
+ const detail = e.detail;
19
+ const next = detail?.value;
20
+ if (!this._controller || !APPROVAL_MODES.includes(next)) return;
21
+ const previous = this._controller.getMode();
22
+ this._controller.setMode(next);
23
+ if (this._controller.getMode() !== previous) {
24
+ this.dispatchEvent(new CustomEvent("aparte-approval-mode-change", {
25
+ bubbles: true,
26
+ composed: true,
27
+ detail: { mode: next, previousMode: previous }
28
+ }));
29
+ }
30
+ };
31
+ /** The label of each mode, for a localised host. Missing entries keep the English default. */
32
+ get labels() {
33
+ return this._labels;
34
+ }
35
+ set labels(next) {
36
+ this._labels = next ?? {};
37
+ if (this.isConnected) this._render();
38
+ }
39
+ /** The current mode, or `null` with no setup. */
40
+ get mode() {
41
+ return this._controller?.getMode() ?? null;
42
+ }
43
+ connectedCallback() {
44
+ this._bind(resolveConfig(this));
45
+ this._configUnsubscribe = subscribeConfigChange(this, () => this._bind(resolveConfig(this)));
46
+ }
47
+ disconnectedCallback() {
48
+ this._unsubscribe?.();
49
+ this._unsubscribe = null;
50
+ this._configUnsubscribe?.();
51
+ this._configUnsubscribe = null;
52
+ this._bound = false;
53
+ this.querySelector("aparte-select")?.removeEventListener("aparte-select-change", this._onChange);
54
+ }
55
+ /** See `AparteConfigAware`: a boundary moved, so the controller may be another one. */
56
+ aparteConfigChanged(next) {
57
+ this._bind(next);
58
+ }
59
+ _bind(cfg) {
60
+ const next = getApprovalController(cfg);
61
+ if (this._bound && cfg === this._cfg && next === this._controller) return;
62
+ this._bound = true;
63
+ this._cfg = cfg;
64
+ this._unsubscribe?.();
65
+ this._controller = next;
66
+ if (!this._controller) {
67
+ console.warn("[aparte] <aparte-approval-mode> found no approval setup on the config it resolves — call setupApproval() from @aparte/plugin-approval on that config (the scoped one if you passed a `config`, aparteGlobalConfig otherwise).");
68
+ }
69
+ this._unsubscribe = this._controller?.subscribe(() => this._syncValue()) ?? null;
70
+ this._render();
71
+ }
72
+ _render() {
73
+ const controller = this._controller;
74
+ const current = controller?.getMode();
75
+ const options = APPROVAL_MODES.map((m) => `<aparte-option value="${escapeAttr(m)}">${escapeHtml(this._labels[m] ?? DEFAULT_LABELS[m])}</aparte-option>`).join("");
76
+ this.querySelector("aparte-select")?.removeEventListener("aparte-select-change", this._onChange);
77
+ const aria = String(this._cfg?.getLocale()["approvalModeLabel"] || "Approval mode");
78
+ this.innerHTML = `<aparte-select${current ? ` value="${escapeAttr(current)}"` : ""}${controller ? "" : " disabled"} aria-label="${escapeAttr(aria)}">${options}</aparte-select>`;
79
+ this.querySelector("aparte-select")?.addEventListener("aparte-select-change", this._onChange);
80
+ }
81
+ _syncValue() {
82
+ const select = this.querySelector("aparte-select");
83
+ const mode = this._controller?.getMode();
84
+ if (select && mode && select.value !== mode) select.value = mode;
85
+ }
86
+ }
87
+ if (typeof customElements !== "undefined" && !customElements.get("aparte-approval-mode")) {
88
+ customElements.define("aparte-approval-mode", AparteApprovalMode);
89
+ }
90
+ export {
91
+ APPROVAL_MODES,
92
+ AparteApprovalMode,
93
+ c as classifyTool,
94
+ a as createApprovalPolicy,
95
+ getApprovalController,
96
+ r as rulingFor,
97
+ s as setupApproval
98
+ };
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/aparte-approval-mode.ts"],"sourcesContent":["/**\n * `<aparte-approval-mode>` — the switch between the four modes, for the composer's toolbar.\n *\n * Mounted where the host wants it, like `<aparte-model-selector>`: the composer is light\n * DOM with no registry, so a control is placed by writing its tag inside\n * `<aparte-composer-toolbar>`. It resolves the config it sits in (`resolveConfig`) and the\n * controller `setupApproval` installed there — with none, it renders disabled and says\n * so once in the console, the way core does for a missing presenter: an affordance that\n * cannot act is not offered.\n */\nimport {\n resolveConfig, subscribeConfigChange, escapeHtml, escapeAttr,\n type AparteConfig, type AparteConfigAware, type AparteSelectChangeDetail,\n type AparteApprovalModeChangeEventDetail,\n} from '@aparte/core';\nimport { getApprovalController, type ApprovalController } from './approval.js';\nimport { APPROVAL_MODES, type ApprovalMode } from './policy.js';\n\nconst DEFAULT_LABELS: Record<ApprovalMode, string> = {\n plan: 'Plan',\n ask: 'Ask',\n 'auto-edit': 'Auto-edit',\n auto: 'Auto',\n};\n\n/**\n * A select over the four approval modes — plan, ask, auto-edit, auto — bound to the\n * `setupApproval()` of the config this element resolves.\n *\n * @element aparte-approval-mode\n *\n * @example Beside the model selector, in the composer's toolbar\n * ```html\n * <aparte-composer>\n * <aparte-composer-input></aparte-composer-input>\n * <aparte-composer-toolbar>\n * <aparte-approval-mode></aparte-approval-mode>\n * </aparte-composer-toolbar>\n * </aparte-composer>\n * ```\n *\n * @fires aparte-approval-mode-change - After a switch: `{ mode, previousMode }`. Bubbles and crosses shadow roots.\n */\nexport class AparteApprovalMode extends HTMLElement implements AparteConfigAware {\n private _cfg: AparteConfig | null = null;\n private _controller: ApprovalController | undefined;\n private _unsubscribe: (() => void) | null = null;\n private _configUnsubscribe: (() => void) | null = null;\n private _bound = false;\n private _labels: Partial<Record<ApprovalMode, string>> = {};\n private readonly _onChange = (e: Event): void => {\n const detail = (e as CustomEvent<AparteSelectChangeDetail>).detail;\n const next = detail?.value as ApprovalMode;\n if (!this._controller || !APPROVAL_MODES.includes(next)) return;\n const previous = this._controller.getMode();\n this._controller.setMode(next);\n if (this._controller.getMode() !== previous) {\n this.dispatchEvent(new CustomEvent<AparteApprovalModeChangeEventDetail>('aparte-approval-mode-change', {\n bubbles: true, composed: true, detail: { mode: next, previousMode: previous },\n }));\n }\n };\n\n /** The label of each mode, for a localised host. Missing entries keep the English default. */\n get labels(): Partial<Record<ApprovalMode, string>> { return this._labels; }\n set labels(next: Partial<Record<ApprovalMode, string>>) {\n this._labels = next ?? {};\n if (this.isConnected) this._render();\n }\n\n /** The current mode, or `null` with no setup. */\n get mode(): ApprovalMode | null { return this._controller?.getMode() ?? null; }\n\n connectedCallback(): void {\n this._bind(resolveConfig(this));\n // A setup that arrives AFTER this element mounted (a later script, a dynamic\n // import), or one that goes away (`dispose()`, `config.reset()`): the config\n // notifies on `setApprovalPolicy`, and the switch re-resolves its controller\n // rather than staying disabled — or wired to nothing — for the life of the page.\n this._configUnsubscribe = subscribeConfigChange(this, () => this._bind(resolveConfig(this)));\n }\n\n disconnectedCallback(): void {\n this._unsubscribe?.();\n this._unsubscribe = null;\n this._configUnsubscribe?.();\n this._configUnsubscribe = null;\n this._bound = false;\n this.querySelector('aparte-select')?.removeEventListener('aparte-select-change', this._onChange);\n }\n\n /** See `AparteConfigAware`: a boundary moved, so the controller may be another one. */\n aparteConfigChanged(next: AparteConfig): void {\n this._bind(next);\n }\n\n private _bind(cfg: AparteConfig): void {\n const next = getApprovalController(cfg);\n // An unrelated notify (a locale, a provider) must not rebuild the select under an\n // open dropdown: same config, same controller, nothing to rebind.\n if (this._bound && cfg === this._cfg && next === this._controller) return;\n this._bound = true;\n this._cfg = cfg;\n this._unsubscribe?.();\n this._controller = next;\n if (!this._controller) {\n console.warn('[aparte] <aparte-approval-mode> found no approval setup on the config it resolves — call setupApproval() from @aparte/plugin-approval on that config (the scoped one if you passed a `config`, aparteGlobalConfig otherwise).');\n }\n this._unsubscribe = this._controller?.subscribe(() => this._syncValue()) ?? null;\n this._render();\n }\n\n private _render(): void {\n const controller = this._controller;\n const current = controller?.getMode();\n const options = APPROVAL_MODES.map((m) =>\n `<aparte-option value=\"${escapeAttr(m)}\">${escapeHtml(this._labels[m] ?? DEFAULT_LABELS[m])}</aparte-option>`).join('');\n this.querySelector('aparte-select')?.removeEventListener('aparte-select-change', this._onChange);\n // The select carries no class of ours: `aparte-approval-mode aparte-select` is\n // already the address a consumer restyling it writes.\n // The accessible name follows the locale like the option labels do — a\n // literal here was the one string a localised host could not translate.\n const aria = String(this._cfg?.getLocale()['approvalModeLabel'] || 'Approval mode');\n this.innerHTML = `<aparte-select${current ? ` value=\"${escapeAttr(current)}\"` : ''}${controller ? '' : ' disabled'} aria-label=\"${escapeAttr(aria)}\">${options}</aparte-select>`;\n this.querySelector('aparte-select')?.addEventListener('aparte-select-change', this._onChange);\n }\n\n private _syncValue(): void {\n const select = this.querySelector('aparte-select') as (HTMLElement & { value: string }) | null;\n const mode = this._controller?.getMode();\n if (select && mode && select.value !== mode) select.value = mode;\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('aparte-approval-mode')) {\n customElements.define('aparte-approval-mode', AparteApprovalMode);\n}\n"],"names":[],"mappings":";;;AAkBA,MAAM,iBAA+C;AAAA,EACjD,MAAM;AAAA,EACN,KAAK;AAAA,EACL,aAAa;AAAA,EACb,MAAM;AACV;AAoBO,MAAM,2BAA2B,YAAyC;AAAA,EACrE,OAA4B;AAAA,EAC5B;AAAA,EACA,eAAoC;AAAA,EACpC,qBAA0C;AAAA,EAC1C,SAAS;AAAA,EACT,UAAiD,CAAA;AAAA,EACxC,YAAY,CAAC,MAAmB;AAC7C,UAAM,SAAU,EAA4C;AAC5D,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAK,eAAe,CAAC,eAAe,SAAS,IAAI,EAAG;AACzD,UAAM,WAAW,KAAK,YAAY,QAAA;AAClC,SAAK,YAAY,QAAQ,IAAI;AAC7B,QAAI,KAAK,YAAY,QAAA,MAAc,UAAU;AACzC,WAAK,cAAc,IAAI,YAAiD,+BAA+B;AAAA,QACnG,SAAS;AAAA,QAAM,UAAU;AAAA,QAAM,QAAQ,EAAE,MAAM,MAAM,cAAc,SAAA;AAAA,MAAS,CAC/E,CAAC;AAAA,IACN;AAAA,EACJ;AAAA;AAAA,EAGA,IAAI,SAAgD;AAAE,WAAO,KAAK;AAAA,EAAS;AAAA,EAC3E,IAAI,OAAO,MAA6C;AACpD,SAAK,UAAU,QAAQ,CAAA;AACvB,QAAI,KAAK,YAAa,MAAK,QAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,IAAI,OAA4B;AAAE,WAAO,KAAK,aAAa,QAAA,KAAa;AAAA,EAAM;AAAA,EAE9E,oBAA0B;AACtB,SAAK,MAAM,cAAc,IAAI,CAAC;AAK9B,SAAK,qBAAqB,sBAAsB,MAAM,MAAM,KAAK,MAAM,cAAc,IAAI,CAAC,CAAC;AAAA,EAC/F;AAAA,EAEA,uBAA6B;AACzB,SAAK,eAAA;AACL,SAAK,eAAe;AACpB,SAAK,qBAAA;AACL,SAAK,qBAAqB;AAC1B,SAAK,SAAS;AACd,SAAK,cAAc,eAAe,GAAG,oBAAoB,wBAAwB,KAAK,SAAS;AAAA,EACnG;AAAA;AAAA,EAGA,oBAAoB,MAA0B;AAC1C,SAAK,MAAM,IAAI;AAAA,EACnB;AAAA,EAEQ,MAAM,KAAyB;AACnC,UAAM,OAAO,sBAAsB,GAAG;AAGtC,QAAI,KAAK,UAAU,QAAQ,KAAK,QAAQ,SAAS,KAAK,YAAa;AACnE,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,eAAA;AACL,SAAK,cAAc;AACnB,QAAI,CAAC,KAAK,aAAa;AACnB,cAAQ,KAAK,+NAA+N;AAAA,IAChP;AACA,SAAK,eAAe,KAAK,aAAa,UAAU,MAAM,KAAK,WAAA,CAAY,KAAK;AAC5E,SAAK,QAAA;AAAA,EACT;AAAA,EAEQ,UAAgB;AACpB,UAAM,aAAa,KAAK;AACxB,UAAM,UAAU,YAAY,QAAA;AAC5B,UAAM,UAAU,eAAe,IAAI,CAAC,MAChC,yBAAyB,WAAW,CAAC,CAAC,KAAK,WAAW,KAAK,QAAQ,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,kBAAkB,EAAE,KAAK,EAAE;AAC1H,SAAK,cAAc,eAAe,GAAG,oBAAoB,wBAAwB,KAAK,SAAS;AAK/F,UAAM,OAAO,OAAO,KAAK,MAAM,YAAY,mBAAmB,KAAK,eAAe;AAClF,SAAK,YAAY,iBAAiB,UAAU,WAAW,WAAW,OAAO,CAAC,MAAM,EAAE,GAAG,aAAa,KAAK,WAAW,gBAAgB,WAAW,IAAI,CAAC,KAAK,OAAO;AAC9J,SAAK,cAAc,eAAe,GAAG,iBAAiB,wBAAwB,KAAK,SAAS;AAAA,EAChG;AAAA,EAEQ,aAAmB;AACvB,UAAM,SAAS,KAAK,cAAc,eAAe;AACjD,UAAM,OAAO,KAAK,aAAa,QAAA;AAC/B,QAAI,UAAU,QAAQ,OAAO,UAAU,aAAa,QAAQ;AAAA,EAChE;AACJ;AAEA,IAAI,OAAO,mBAAmB,eAAe,CAAC,eAAe,IAAI,sBAAsB,GAAG;AACtF,iBAAe,OAAO,wBAAwB,kBAAkB;AACpE;"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@aparte/plugin-approval` — the DOM-free entry, for Node and SSR.
3
+ *
4
+ * Everything a server can legitimately use: the setup, the controller, the table.
5
+ * What is NOT here is `<aparte-approval-mode>`, which needs a document. Resolved
6
+ * through core's `node` condition, `import '@aparte/plugin-approval'` on a server
7
+ * registers the policy and nothing else — `scripts/check-node-import.mjs` asserts
8
+ * this entry keeps importing without a DOM.
9
+ */
10
+ export { setupApproval, getApprovalController } from './approval.js';
11
+ export type { ApprovalSetupOptions, ApprovalController } from './approval.js';
12
+ export { APPROVAL_MODES, classifyTool, rulingFor, createApprovalPolicy } from './policy.js';
13
+ export type { ApprovalMode, ToolClass, ToolMatcher, ApprovalClassification } from './policy.js';
14
+ export type { AparteApprovalMode } from './aparte-approval-mode.js';
15
+ export type { AparteApprovalPolicy, AparteApprovalRuling, AparteToolCall, AparteApprovalModeChangeEventDetail, } from '@aparte/core';
16
+ //# sourceMappingURL=index.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.node.d.ts","sourceRoot":"","sources":["../src/index.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACrE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAGhG,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EACR,oBAAoB,EAAE,oBAAoB,EAAE,cAAc,EAAE,mCAAmC,GAClG,MAAM,cAAc,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { A, c, a, g, r, s } from "./approval-B7URDqOS.js";
2
+ export {
3
+ A as APPROVAL_MODES,
4
+ c as classifyTool,
5
+ a as createApprovalPolicy,
6
+ g as getApprovalController,
7
+ r as rulingFor,
8
+ s as setupApproval
9
+ };
10
+ //# sourceMappingURL=index.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.node.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The modes, the classes, and the table that joins them.
3
+ *
4
+ * A MODE is what the person switches ("plan", "auto"); a CLASS is what a tool does
5
+ * (`read`, `write`, `exec`), declared by the host on ITS tool names — the names are
6
+ * wire format the host owns, and no library can know that `run_command` executes and
7
+ * `search_docs` reads. The table below is the whole policy: nine cells, no
8
+ * configuration, so a consumer can read it in one glance and predict every ruling.
9
+ *
10
+ * Nothing here executes anything, remembers anything, or draws anything: it is a pure
11
+ * function from (mode, class, call) to a ruling core already knows how to honour.
12
+ */
13
+ import type { AparteApprovalPolicy, AparteApprovalRuling, AparteToolCall } from '@aparte/core';
14
+ /**
15
+ * `plan` runs read-only tools and refuses the rest with a sentence the model reads;
16
+ * `ask` asks before every write or execution; `auto-edit` lets writes through and asks
17
+ * before an execution; `auto` never asks.
18
+ */
19
+ export type ApprovalMode = 'plan' | 'ask' | 'auto-edit' | 'auto';
20
+ /** The four modes, in the order a switch presents them. */
21
+ export declare const APPROVAL_MODES: readonly ApprovalMode[];
22
+ /** What a tool does — the axis the modes decide on. */
23
+ export type ToolClass = 'read' | 'write' | 'exec';
24
+ /** A tool name, exactly, or a pattern over names. */
25
+ export type ToolMatcher = string | RegExp;
26
+ /**
27
+ * The host's tool names, by what they do. A name in no list is UNCLASSIFIED: its
28
+ * own `needsApproval` decides (and `auto` still lets it through — auto is auto).
29
+ */
30
+ export interface ApprovalClassification {
31
+ read?: readonly ToolMatcher[];
32
+ write?: readonly ToolMatcher[];
33
+ exec?: readonly ToolMatcher[];
34
+ }
35
+ /** The class of a tool name under a classification, or `undefined` when it is in no list. */
36
+ export declare function classifyTool(name: string, classification: ApprovalClassification): ToolClass | undefined;
37
+ /**
38
+ * The ruling for one call, from the table. `undefined` means "no opinion" — the tool's
39
+ * own `needsApproval` decides, which is what core does when a policy says nothing.
40
+ */
41
+ export declare function rulingFor(mode: ApprovalMode, cls: ToolClass | undefined, call: AparteToolCall): AparteApprovalRuling | undefined;
42
+ /**
43
+ * A core `AparteApprovalPolicy` that reads the CURRENT mode on every call — so a
44
+ * switch flipped mid-run applies to the next tool call, not the next conversation.
45
+ */
46
+ export declare function createApprovalPolicy(getMode: () => ApprovalMode, classification: ApprovalClassification): AparteApprovalPolicy;
47
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE/F;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,MAAM,CAAC;AAEjE,2DAA2D;AAC3D,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAAyC,CAAC;AAE5F,uDAAuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACjC;AAQD,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAKxG;AAID;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,oBAAoB,GAAG,SAAS,CAahI;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,YAAY,EAAE,cAAc,EAAE,sBAAsB,GAAG,oBAAoB,CAE9H"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@aparte/plugin-approval",
3
+ "version": "0.16.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Approval modes for aparté tool calls — plan, ask, auto-edit, auto — from a classification of your tool names, with a switch for the composer's toolbar.",
8
+ "customElements": "dist/custom-elements.json",
9
+ "type": "module",
10
+ "sideEffects": true,
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "@aparte-workspace/source": "./src/index.ts",
17
+ "node": {
18
+ "types": "./dist/index.node.d.ts",
19
+ "default": "./dist/index.node.js"
20
+ },
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "peerDependencies": {
35
+ "@aparte/core": ">=0.16.0 <1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@custom-elements-manifest/analyzer": "^0.11.0",
39
+ "typescript": "^5.4.0",
40
+ "vite": "^6.0.0",
41
+ "@aparte/core": "0.16.0"
42
+ },
43
+ "keywords": [
44
+ "aparte",
45
+ "plugin",
46
+ "tool",
47
+ "approval",
48
+ "human-in-the-loop",
49
+ "agent"
50
+ ],
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/apartejs/aparte.git",
55
+ "directory": "packages/plugins/approval"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/apartejs/aparte/issues"
59
+ },
60
+ "scripts": {
61
+ "dev": "vite",
62
+ "build": "vite build && tsc -b --emitDeclarationOnly --force && pnpm run analyze",
63
+ "analyze": "cem analyze",
64
+ "preview": "vite preview",
65
+ "test": "vitest",
66
+ "test:run": "vitest run",
67
+ "test:coverage": "vitest run --coverage"
68
+ }
69
+ }