@agimon-ai/po-approval 0.0.0-alpha.43

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.
@@ -0,0 +1,187 @@
1
+ import { defineFill, defineSlotFile, defineWebPlugin } from "@agimon-ai/po-core/web";
2
+ import { Button } from "@agimon-ai/po-web-components";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useEffect, useState, useSyncExternalStore } from "react";
5
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ApprovalPromptView.tsx
6
+ function ApprovalPromptView({ title, summary, details, requirement, seconds, expired, decision, disabled, onApprove, onDeny, onCancel, voiceHint }) {
7
+ const inactive = disabled || expired || Boolean(decision);
8
+ return /* @__PURE__ */ jsxs("section", {
9
+ "aria-label": "Approval request",
10
+ onKeyDown: (event) => {
11
+ if (event.key === "Escape" && !inactive) onCancel();
12
+ },
13
+ className: "mx-auto flex max-w-2xl flex-col gap-4 rounded-lg border border-po-edge-magenta bg-po-panel p-4",
14
+ children: [
15
+ /* @__PURE__ */ jsxs("div", {
16
+ className: "flex flex-col gap-1",
17
+ children: [
18
+ /* @__PURE__ */ jsx("span", {
19
+ className: "text-sm font-medium text-po-magenta",
20
+ children: "Your approval"
21
+ }),
22
+ /* @__PURE__ */ jsx("h2", {
23
+ className: "text-lg font-semibold text-po-hi",
24
+ children: title
25
+ }),
26
+ /* @__PURE__ */ jsx("p", {
27
+ className: "text-lg leading-relaxed",
28
+ children: summary
29
+ })
30
+ ]
31
+ }),
32
+ /* @__PURE__ */ jsx("dl", {
33
+ className: "flex flex-col gap-2 text-base",
34
+ children: details.map(({ label, value }) => /* @__PURE__ */ jsxs("div", {
35
+ className: "grid grid-cols-1 gap-1 sm:grid-cols-3 sm:gap-3",
36
+ children: [/* @__PURE__ */ jsx("dt", {
37
+ className: "text-po-dim",
38
+ children: label
39
+ }), /* @__PURE__ */ jsx("dd", {
40
+ className: "break-words text-po-hi sm:col-span-2",
41
+ children: value
42
+ })]
43
+ }, label))
44
+ }),
45
+ /* @__PURE__ */ jsxs("div", {
46
+ className: "flex flex-col gap-3 border-t border-po-border-soft pt-3",
47
+ children: [/* @__PURE__ */ jsx("p", {
48
+ className: "text-sm text-po-dim",
49
+ role: "status",
50
+ "aria-live": "polite",
51
+ children: decision || (expired ? "This request expired. Nothing ran." : seconds !== void 0 ? `${seconds} seconds remaining. Nothing runs if time runs out.` : requirement === "human" ? "Only you can approve this in the app." : "Po will wait for your decision.")
52
+ }), /* @__PURE__ */ jsxs("div", {
53
+ className: "flex flex-wrap items-center gap-2",
54
+ children: [
55
+ /* @__PURE__ */ jsx(Button, {
56
+ variant: "primary",
57
+ disabled: inactive,
58
+ onClick: onApprove,
59
+ children: "Approve"
60
+ }),
61
+ /* @__PURE__ */ jsx(Button, {
62
+ variant: "outline",
63
+ disabled: inactive,
64
+ onClick: onDeny,
65
+ children: "Deny"
66
+ }),
67
+ requirement === "ask" && voiceHint && !inactive ? /* @__PURE__ */ jsx("span", {
68
+ className: "text-sm text-po-dim",
69
+ children: voiceHint
70
+ }) : null
71
+ ]
72
+ })]
73
+ })
74
+ ]
75
+ });
76
+ }
77
+ //#endregion
78
+ //#region src/types/voiceHint.ts
79
+ function parseApprovalVoiceHintSource(input) {
80
+ const source = input;
81
+ return typeof source?.subscribe === "function" && typeof source.isActive === "function" ? source : null;
82
+ }
83
+ /** Voice fills this while installed; the hint shows only for `ask` approvals and only while a source is active. */
84
+ const approvalVoiceHintSlot = {
85
+ slot: "approval.voice-hint",
86
+ parse: parseApprovalVoiceHintSource
87
+ };
88
+ //#endregion
89
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/approvalDeadline.ts
90
+ /** Whole seconds left before `expiresAt`, or undefined when the request has no deadline. */
91
+ function remainingSeconds(expiresAt, now) {
92
+ return expiresAt === void 0 ? void 0 : Math.max(0, Math.ceil((expiresAt - now) / 1e3));
93
+ }
94
+ //#endregion
95
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/useApprovalPrompt.ts
96
+ const VOICE_HINT = "Or say “approve” or “deny”.";
97
+ const NO_SOURCES = () => () => void 0;
98
+ /** Display-only countdown. The server's deadline decides; this only stops offering a stale choice. */
99
+ function useDeadline(expiresAt) {
100
+ const [seconds, setSeconds] = useState(() => remainingSeconds(expiresAt, Date.now()));
101
+ useEffect(() => {
102
+ setSeconds(remainingSeconds(expiresAt, Date.now()));
103
+ if (expiresAt === void 0) return void 0;
104
+ const timer = setInterval(() => {
105
+ const next = remainingSeconds(expiresAt, Date.now());
106
+ setSeconds(next);
107
+ if (next === 0) clearInterval(timer);
108
+ }, 1e3);
109
+ return () => clearInterval(timer);
110
+ }, [expiresAt]);
111
+ return seconds;
112
+ }
113
+ function useVoiceActive(sources, sessionId) {
114
+ const subscribe = sources.length === 0 ? NO_SOURCES : (listener) => {
115
+ const stops = sources.map((source) => source.subscribe(listener));
116
+ return () => {
117
+ for (const stop of stops) stop();
118
+ };
119
+ };
120
+ const read = () => sources.some((source) => source.isActive(sessionId));
121
+ return useSyncExternalStore(subscribe, read, read);
122
+ }
123
+ /** The approved view's props for one live dialog. Callbacks answer with the exact select labels. */
124
+ function useApprovalPrompt(props) {
125
+ const approval = props.dialog.approval;
126
+ const seconds = useDeadline(approval?.expiresAt);
127
+ const voice = useVoiceActive(props.slotData(approvalVoiceHintSlot).map((fill) => fill.data), props.sessionId);
128
+ if (approval === void 0) return void 0;
129
+ return {
130
+ title: approval.title,
131
+ summary: approval.summary,
132
+ details: approval.details ?? [],
133
+ requirement: approval.requirement,
134
+ ...seconds === void 0 || seconds === 0 ? {} : { seconds },
135
+ expired: seconds === 0,
136
+ onApprove: () => props.answer("Approve"),
137
+ onDeny: () => props.answer("Deny"),
138
+ onCancel: props.cancel,
139
+ ...approval.requirement === "ask" && voice ? { voiceHint: VOICE_HINT } : {}
140
+ };
141
+ }
142
+ //#endregion
143
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ApprovalDialogPrompt.tsx
144
+ /** The approved inline prompt, replacing the composer while the session waits on one approval. */
145
+ function ApprovalDialogPrompt(props) {
146
+ const view = useApprovalPrompt(props);
147
+ return view === void 0 ? null : /* @__PURE__ */ jsx(ApprovalPromptView, { ...view });
148
+ }
149
+ //#endregion
150
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/approvalClaim.ts
151
+ /** Only a select carrying a parsed approval envelope with both exact choices. */
152
+ function claimsApproval(dialog) {
153
+ return dialog.method === "select" && dialog.approval !== void 0 && dialog.options.includes("Approve") && dialog.options.includes("Deny");
154
+ }
155
+ //#endregion
156
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/Approval.dialog-prompt.web.tsx
157
+ var Approval_dialog_prompt_web_default = defineFill({ data: {
158
+ claims: claimsApproval,
159
+ component: ApprovalDialogPrompt
160
+ } });
161
+ //#endregion
162
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/voice-hint.web.ts
163
+ var voice_hint_web_default = defineSlotFile(approvalVoiceHintSlot);
164
+ //#endregion
165
+ //#region generated/web.ts
166
+ const via = (identity, value) => value === void 0 ? void 0 : {
167
+ ...identity,
168
+ ...value
169
+ };
170
+ const webPlugin = defineWebPlugin({
171
+ id: "approval",
172
+ session: {
173
+ get fills() {
174
+ return [via({
175
+ slot: "dialog-prompt",
176
+ id: "approval"
177
+ }, Approval_dialog_prompt_web_default)];
178
+ },
179
+ get slots() {
180
+ return [via({ slot: "approval.voice-hint" }, voice_hint_web_default)];
181
+ }
182
+ }
183
+ });
184
+ //#endregion
185
+ export { webPlugin };
186
+
187
+ //# sourceMappingURL=web.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.mjs","names":["fillApproval","slotVoiceHint"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/_components/ApprovalPromptView.tsx","../../src/types/voiceHint.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/approvalDeadline.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/useApprovalPrompt.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/ApprovalDialogPrompt.tsx","../../src/extensions/workspaces/sessions/(frontend)/_lib/approvalClaim.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/Approval.dialog-prompt.web.tsx","../../src/extensions/workspaces/sessions/(frontend)/slot/voice-hint.web.ts","../../generated/web.ts"],"sourcesContent":["import { Button } from '@agimon-ai/po-web-components';\n\nimport type { ApprovalPromptViewProps } from './ApprovalPromptView.types';\n\nexport function ApprovalPromptView({\n title,\n summary,\n details,\n requirement,\n seconds,\n expired,\n decision,\n disabled,\n onApprove,\n onDeny,\n onCancel,\n voiceHint,\n}: ApprovalPromptViewProps) {\n const inactive = disabled || expired || Boolean(decision);\n return (\n <section\n aria-label=\"Approval request\"\n onKeyDown={(event) => {\n if (event.key === 'Escape' && !inactive) onCancel();\n }}\n className=\"mx-auto flex max-w-2xl flex-col gap-4 rounded-lg border border-po-edge-magenta bg-po-panel p-4\"\n >\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-sm font-medium text-po-magenta\">Your approval</span>\n <h2 className=\"text-lg font-semibold text-po-hi\">{title}</h2>\n <p className=\"text-lg leading-relaxed\">{summary}</p>\n </div>\n <dl className=\"flex flex-col gap-2 text-base\">\n {details.map(({ label, value }) => (\n <div key={label} className=\"grid grid-cols-1 gap-1 sm:grid-cols-3 sm:gap-3\">\n <dt className=\"text-po-dim\">{label}</dt>\n <dd className=\"break-words text-po-hi sm:col-span-2\">{value}</dd>\n </div>\n ))}\n </dl>\n <div className=\"flex flex-col gap-3 border-t border-po-border-soft pt-3\">\n <p className=\"text-sm text-po-dim\" role=\"status\" aria-live=\"polite\">\n {decision ||\n (expired\n ? 'This request expired. Nothing ran.'\n : seconds !== undefined\n ? `${seconds} seconds remaining. Nothing runs if time runs out.`\n : requirement === 'human'\n ? 'Only you can approve this in the app.'\n : 'Po will wait for your decision.')}\n </p>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button variant=\"primary\" disabled={inactive} onClick={onApprove}>\n Approve\n </Button>\n <Button variant=\"outline\" disabled={inactive} onClick={onDeny}>\n Deny\n </Button>\n {requirement === 'ask' && voiceHint && !inactive ? (\n <span className=\"text-sm text-po-dim\">{voiceHint}</span>\n ) : null}\n </div>\n </div>\n </section>\n );\n}\n","import type { SlotDeclaration } from '@agimon-ai/po-core/web';\n\n/** Whether a voice surface can take a spoken approve or deny for a session right now. */\nexport interface ApprovalVoiceHintSource {\n subscribe(listener: () => void): () => void;\n isActive(sessionId: string | null): boolean;\n}\n\nfunction parseApprovalVoiceHintSource(input: unknown): ApprovalVoiceHintSource | null {\n const source = input as Partial<ApprovalVoiceHintSource> | null;\n return typeof source?.subscribe === 'function' && typeof source.isActive === 'function'\n ? (source as ApprovalVoiceHintSource)\n : null;\n}\n\n/** Voice fills this while installed; the hint shows only for `ask` approvals and only while a source is active. */\nexport const approvalVoiceHintSlot: SlotDeclaration<ApprovalVoiceHintSource> = {\n slot: 'approval.voice-hint',\n parse: parseApprovalVoiceHintSource,\n};\n","/** Whole seconds left before `expiresAt`, or undefined when the request has no deadline. */\nexport function remainingSeconds(expiresAt: number | undefined, now: number): number | undefined {\n return expiresAt === undefined ? undefined : Math.max(0, Math.ceil((expiresAt - now) / 1000));\n}\n","import type { DialogPromptRenderProps } from '@agimon-ai/po-core/web';\nimport { useEffect, useState, useSyncExternalStore } from 'react';\n\nimport type { ApprovalVoiceHintSource } from '../../../../../types/voiceHint';\nimport { approvalVoiceHintSlot } from '../../../../../types/voiceHint';\nimport { remainingSeconds } from '../_lib/approvalDeadline';\nimport type { ApprovalPromptViewProps } from './ApprovalPromptView.types';\n\nconst VOICE_HINT = 'Or say “approve” or “deny”.';\nconst NO_SOURCES = (): (() => void) => () => undefined;\n\n/** Display-only countdown. The server's deadline decides; this only stops offering a stale choice. */\nfunction useDeadline(expiresAt: number | undefined): number | undefined {\n const [seconds, setSeconds] = useState(() => remainingSeconds(expiresAt, Date.now()));\n useEffect(() => {\n setSeconds(remainingSeconds(expiresAt, Date.now()));\n if (expiresAt === undefined) return undefined;\n const timer = setInterval(() => {\n const next = remainingSeconds(expiresAt, Date.now());\n setSeconds(next);\n if (next === 0) clearInterval(timer);\n }, 1000);\n return () => clearInterval(timer);\n }, [expiresAt]);\n return seconds;\n}\n\nfunction useVoiceActive(sources: readonly ApprovalVoiceHintSource[], sessionId: string | null): boolean {\n const subscribe =\n sources.length === 0\n ? NO_SOURCES\n : (listener: () => void) => {\n const stops = sources.map((source) => source.subscribe(listener));\n return () => {\n for (const stop of stops) stop();\n };\n };\n const read = () => sources.some((source) => source.isActive(sessionId));\n return useSyncExternalStore(subscribe, read, read);\n}\n\n/** The approved view's props for one live dialog. Callbacks answer with the exact select labels. */\nexport function useApprovalPrompt(props: DialogPromptRenderProps): ApprovalPromptViewProps | undefined {\n const approval = props.dialog.approval;\n const seconds = useDeadline(approval?.expiresAt);\n const voice = useVoiceActive(\n props.slotData(approvalVoiceHintSlot).map((fill) => fill.data),\n props.sessionId,\n );\n if (approval === undefined) return undefined;\n return {\n title: approval.title,\n summary: approval.summary,\n details: approval.details ?? [],\n requirement: approval.requirement,\n ...(seconds === undefined || seconds === 0 ? {} : { seconds }),\n expired: seconds === 0,\n onApprove: () => props.answer('Approve'),\n onDeny: () => props.answer('Deny'),\n onCancel: props.cancel,\n ...(approval.requirement === 'ask' && voice ? { voiceHint: VOICE_HINT } : {}),\n };\n}\n","import type { DialogPromptRenderProps } from '@agimon-ai/po-core/web';\n\nimport { ApprovalPromptView } from './ApprovalPromptView';\nimport { useApprovalPrompt } from './useApprovalPrompt';\n\n/** The approved inline prompt, replacing the composer while the session waits on one approval. */\nexport function ApprovalDialogPrompt(props: DialogPromptRenderProps) {\n const view = useApprovalPrompt(props);\n return view === undefined ? null : <ApprovalPromptView {...view} />;\n}\n","import type { ToolPromptDialog } from '@agimon-ai/po-core/web';\n\n/** Only a select carrying a parsed approval envelope with both exact choices. */\nexport function claimsApproval(dialog: ToolPromptDialog): boolean {\n return (\n dialog.method === 'select' &&\n dialog.approval !== undefined &&\n dialog.options.includes('Approve') &&\n dialog.options.includes('Deny')\n );\n}\n","import { defineFill } from '@agimon-ai/po-core/web';\n\nimport { ApprovalDialogPrompt } from '../_components/ApprovalDialogPrompt';\nimport { claimsApproval } from '../_lib/approvalClaim';\n\nexport default defineFill({ data: { claims: claimsApproval, component: ApprovalDialogPrompt } });\n","import { defineSlotFile } from '@agimon-ai/po-core/web';\n\nimport { approvalVoiceHintSlot } from '../../../../../types/voiceHint';\n\nexport default defineSlotFile(approvalVoiceHintSlot);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-core/web';\n\nimport fillApproval from '../src/extensions/workspaces/sessions/(frontend)/fill/Approval.dialog-prompt.web';\nimport slotVoiceHint from '../src/extensions/workspaces/sessions/(frontend)/slot/voice-hint.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'approval',\n session: {\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'dialog-prompt', id: 'approval' }, fillApproval)]; },\n get slots(): WebPluginContributions['slots'] { return [via({ slot: 'approval.voice-hint' }, slotVoiceHint)]; },\n },\n});\n"],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_approvalCoordinator = require("./approvalCoordinator-Be6utzFg.cjs");
3
+ exports.APPROVAL_OPTIONS = require_approvalCoordinator.APPROVAL_OPTIONS;
4
+ exports.createApprovalCoordinator = require_approvalCoordinator.createApprovalCoordinator;
@@ -0,0 +1,19 @@
1
+ import { PoApprovalService } from "@agimon-ai/po-core/approval";
2
+ import { PoHeadlessClient } from "@agimon-ai/po-core/headless";
3
+ //#region src/services/approvalCoordinator/index.d.ts
4
+ /** The options of every approval dialog. Clients match the labels; the values come back as the decision. */
5
+ export declare const APPROVAL_OPTIONS: readonly (Readonly<{
6
+ label: "Approve";
7
+ value: "approved";
8
+ }> | Readonly<{
9
+ label: "Deny";
10
+ value: "denied";
11
+ }>)[];
12
+ /**
13
+ * Asks the person through the session's dialog channel: one Approve/Deny `select` per call, carrying the bounded
14
+ * approval card and never the call's arguments. Resolves `approved` only from an explicit Approve that arrived before
15
+ * abort or expiry; everything else, including a closed client, is `cancelled` or `denied`, so nothing runs.
16
+ */
17
+ export declare function createApprovalCoordinator(client: () => PoHeadlessClient | undefined): PoApprovalService;
18
+ //#endregion
19
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/services/approvalCoordinator/index.ts"],"mappings":";;;;qBAYa,4BAAgB;;;KAAA;;;;;;;;;wBAUb,0BAA0B,cAAc,+BAA+B"}
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export { t as APPROVAL_OPTIONS, n as createApprovalCoordinator } from "./approvalCoordinator-CRbOm-PY.mjs";
package/llms.txt ADDED
@@ -0,0 +1,7 @@
1
+ # Po Approval
2
+
3
+ Asks the person before a tool or code-mode method that declares approval runs in a Po session.
4
+
5
+ ## Resources
6
+
7
+ - [Package README](./README.md): the `po/approval` session service, how a call is asked, approved, denied, cancelled or expired, and where nobody can answer.
package/package.json ADDED
@@ -0,0 +1,145 @@
1
+ {
2
+ "name": "@agimon-ai/po-approval",
3
+ "version": "0.0.0-alpha.43",
4
+ "description": "Asks the person before a tool or code-mode method that declares approval runs in a Po session.",
5
+ "keywords": [
6
+ "agent-safety",
7
+ "ai",
8
+ "approval",
9
+ "coding-agent",
10
+ "developer-tools",
11
+ "human-in-the-loop",
12
+ "po"
13
+ ],
14
+ "homepage": "https://agimon.ai",
15
+ "bugs": {
16
+ "url": "https://github.com/AgiFlow/po/issues"
17
+ },
18
+ "license": "BUSL-1.1",
19
+ "author": "Vuong Ngo",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/AgiFlow/po.git",
23
+ "directory": "packages/foundations/po-approval"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "llms.txt",
28
+ "README.md",
29
+ "LICENSE",
30
+ "package.json"
31
+ ],
32
+ "type": "module",
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.mjs",
35
+ "types": "./dist/index.d.mts",
36
+ "jsnext:main": "./dist/index.mjs",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.mts",
40
+ "import": "./dist/index.mjs",
41
+ "require": "./dist/index.cjs"
42
+ },
43
+ "./apiContracts": {
44
+ "types": "./dist/apiContracts.d.mts",
45
+ "import": "./dist/apiContracts.mjs",
46
+ "require": "./dist/apiContracts.cjs"
47
+ },
48
+ "./extensions/native": {
49
+ "import": "./dist/extensions/native.mjs"
50
+ },
51
+ "./extensions/server": {
52
+ "types": "./dist/extensions/server.d.mts",
53
+ "import": "./dist/extensions/server.mjs",
54
+ "require": "./dist/extensions/server.cjs"
55
+ },
56
+ "./extensions/web": {
57
+ "import": "./dist/extensions/web.mjs"
58
+ },
59
+ "./package.json": "./package.json"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "dependencies": {
65
+ "@agimon-ai/po-core": "0.0.0-alpha.43",
66
+ "@agimon-ai/po-web-components": "0.0.0-alpha.43",
67
+ "@deepseek-ai/cordis": "4.0.5",
68
+ "typebox": "1.4.2"
69
+ },
70
+ "devDependencies": {
71
+ "@agimon-ai/po-build": "0.0.0-alpha.43",
72
+ "@agimon-ai/po-native-components": "0.0.0-alpha.43",
73
+ "@agimon-ai/vibe-lint-plugin-po-extension": "0.0.0-alpha.43",
74
+ "@types/node": "26.6.5",
75
+ "@types/react": "19.3.0",
76
+ "@types/react-dom": "19.3.0",
77
+ "@vitest/coverage-v8": "5.0.4",
78
+ "react": "19.3.1",
79
+ "react-dom": "19.3.1",
80
+ "react-native": "0.88.0",
81
+ "react-native-unistyles": "3.5.3",
82
+ "react-native-web": "0.21.4",
83
+ "tsdown": "0.23.1",
84
+ "typescript": "7.0.2",
85
+ "vitest": "5.0.4"
86
+ },
87
+ "peerDependencies": {
88
+ "@agimon-ai/po-native-components": "0.0.0-alpha.43",
89
+ "react-native": "0.88.0",
90
+ "react-native-unistyles": "^3.5.3"
91
+ },
92
+ "peerDependenciesMeta": {
93
+ "@agimon-ai/po-native-components": {
94
+ "optional": true
95
+ },
96
+ "react-native": {
97
+ "optional": true
98
+ },
99
+ "react-native-unistyles": {
100
+ "optional": true
101
+ }
102
+ },
103
+ "engines": {
104
+ "node": ">=24"
105
+ },
106
+ "poNative": {
107
+ "pluginId": "approval",
108
+ "channels": [],
109
+ "client": "./dist/extensions/native.mjs",
110
+ "scopes": [
111
+ "session"
112
+ ]
113
+ },
114
+ "poServer": {
115
+ "entry": "./generated/server.ts",
116
+ "dist": "./dist/extensions/server.mjs",
117
+ "scopes": [
118
+ "session"
119
+ ],
120
+ "contracts": {
121
+ "entry": "./src/exports/apiContracts.ts",
122
+ "dist": "./dist/apiContracts.mjs"
123
+ },
124
+ "provides": {
125
+ "session": [
126
+ "po/approval"
127
+ ]
128
+ }
129
+ },
130
+ "poWeb": {
131
+ "pluginId": "approval",
132
+ "channels": [],
133
+ "client": "./dist/extensions/web.mjs",
134
+ "scopes": [
135
+ "session"
136
+ ]
137
+ },
138
+ "scripts": {
139
+ "build": "tsdown",
140
+ "test": "vitest --run",
141
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json && tsc --noEmit -p tsconfig.native.json",
142
+ "lint": "oxlint . && oxfmt . --check",
143
+ "fixcode": "oxlint . --fix && oxfmt ."
144
+ }
145
+ }