esoul-sdk 0.3.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.
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Server half of a plugin package (`server.ts` in your plugin folder).
3
+ * Runs ONLY inside the ExternalSoul host (imported by the generic
4
+ * webhook/op routes) — it may use prisma, node crypto, provider SDKs.
5
+ *
6
+ * Outside the host these exports are typed stubs that throw with a clear
7
+ * message, so an author's unit tests fail loudly rather than mysteriously.
8
+ * Inside the host, `esoul-sdk/server` is aliased to the real
9
+ * implementations.
10
+ */
11
+ export interface PluginWebhookContext {
12
+ /** Raw request — read body/headers; VERIFY THE PROVIDER'S SIGNATURE
13
+ * YOURSELF (timing-safe!). The platform does no auth on webhooks. */
14
+ request: Request;
15
+ method: string;
16
+ pluginId: string;
17
+ hookName: string;
18
+ /** Kick one of THIS plugin's durable tasks
19
+ * (`<applicationType>/<taskName>` only — no cross-app injection). */
20
+ sendInngestEvent(name: string, data: Record<string, unknown>): Promise<void>;
21
+ }
22
+ export type PluginWebhookHandler = (ctx: PluginWebhookContext) => Promise<Response>;
23
+ export interface PluginOpContext {
24
+ pluginId: string;
25
+ opName: string;
26
+ workspaceId: string;
27
+ nodeId: string;
28
+ instanceName: string;
29
+ /** The instance's bound connection (or null). Resolve credentials via
30
+ * getPluginConnectionCredentials. */
31
+ cloudConnectionId: string | null;
32
+ /** Caller-supplied args — validate before use. */
33
+ args: unknown;
34
+ }
35
+ export type PluginOpHandler = (ctx: PluginOpContext) => Promise<unknown>;
36
+ export interface PluginServerModule {
37
+ webhooks?: Record<string, PluginWebhookHandler>;
38
+ ops?: Record<string, PluginOpHandler>;
39
+ }
40
+ export type PluginConnectionCredentials = {
41
+ kind: "oauth2";
42
+ accessToken: string;
43
+ } | {
44
+ kind: "apiKey";
45
+ apiKey?: string;
46
+ headers?: Record<string, string>;
47
+ };
48
+ /**
49
+ * Read the sealed credentials of a plugin-declared connection (auto-
50
+ * refreshes expiring OAuth tokens). HOST-ONLY.
51
+ */
52
+ export declare function getPluginConnectionCredentials(_connectionId: string, _pluginId: string): Promise<PluginConnectionCredentials>;
53
+ export interface EmitPluginAppEventArgs {
54
+ source: {
55
+ pluginId: string;
56
+ workspaceId: string;
57
+ nodeId: string;
58
+ applicationType: string;
59
+ };
60
+ targetNodeId: string;
61
+ eventName: string;
62
+ eventData: Record<string, unknown>;
63
+ }
64
+ /**
65
+ * Cross-app events: dispatch the TARGET app's own events through the
66
+ * platform spine (target's dataCreator mints; triggers fire; every event is
67
+ * actor-stamped `{kind:"plugin", pluginId, sourceNodeId}`). Same-workspace
68
+ * only. HOST-ONLY.
69
+ */
70
+ /**
71
+ * Read one app's state folded to head, by nodeId — server truth for an op or
72
+ * a task. Null when no such app exists. HOST-ONLY.
73
+ */
74
+ export declare function readAppState(_nodeId: string): Promise<{
75
+ nodeId: string;
76
+ workspaceId: string;
77
+ applicationType: string;
78
+ foldedSeq: number;
79
+ state: Record<string, unknown>;
80
+ } | null>;
81
+ export interface CallWorkspaceToolArgs {
82
+ pluginId: string;
83
+ nodeId: string;
84
+ tool: string;
85
+ args?: Record<string, unknown>;
86
+ appType?: string;
87
+ targetNodeId?: string;
88
+ }
89
+ /**
90
+ * Call another app's tool from the server half (an op, task or webhook) —
91
+ * how an app orchestrates a my_computer or writes a spreadsheet from a job.
92
+ * Gated by the manifest's `workspaceTools` grants, same-workspace only.
93
+ * HOST-ONLY.
94
+ */
95
+ export declare function callWorkspaceTool(_a: CallWorkspaceToolArgs): Promise<{
96
+ ok: boolean;
97
+ text: string;
98
+ }>;
99
+ export declare function emitPluginAppEvent(_args: EmitPluginAppEventArgs): Promise<{
100
+ ok: true;
101
+ targetNodeId: string;
102
+ eventName: string;
103
+ }>;
104
+ import type { FileEntry, FileRef, FileSource } from "./files.js";
105
+ export interface FileProviderContext {
106
+ workspaceId: string;
107
+ userId: string;
108
+ connectionId?: string;
109
+ localMount?: {
110
+ id: string;
111
+ path: string;
112
+ label?: string;
113
+ };
114
+ }
115
+ export interface FileReadResult {
116
+ bytes: Buffer;
117
+ name: string;
118
+ contentType?: string;
119
+ }
120
+ /**
121
+ * A file provider your plugin CONTRIBUTES (manifest `fileProviders` +
122
+ * `pluginServer.fileProviders[key]`): the browse/read half of a mount backed
123
+ * by one of your declared connections. The host derives one mount per
124
+ * ACTIVE connection and calls you with `ctx.connectionId` set — resolve
125
+ * credentials yourself via `getPluginConnectionCredentials`. A backend that
126
+ * cannot answer must THROW FileSourceError("source_unavailable"), never
127
+ * return an empty listing; reads must respect `capBytes`.
128
+ */
129
+ export interface PluginFileProviderImpl {
130
+ list(ctx: FileProviderContext, sourceId: string, folderRef?: string, cursor?: string): Promise<{
131
+ entries: FileEntry[];
132
+ next?: string;
133
+ }>;
134
+ read(ctx: FileProviderContext, sourceId: string, ref: string, capBytes: number): Promise<FileReadResult>;
135
+ }
136
+ export interface FilesApi {
137
+ sources(): Promise<FileSource[]>;
138
+ list(sourceId: string, folderRef?: string, cursor?: string): Promise<{
139
+ entries: FileEntry[];
140
+ next?: string;
141
+ }>;
142
+ read(ref: FileRef, opts?: {
143
+ capBytes?: number;
144
+ }): Promise<{
145
+ bytes: Buffer;
146
+ name: string;
147
+ contentType?: string;
148
+ }>;
149
+ getUrl(ref: FileRef): Promise<string | null>;
150
+ saveWorkspaceFile(args: {
151
+ name: string;
152
+ bytes: Buffer | Uint8Array | string;
153
+ contentType?: string;
154
+ folderPath?: string;
155
+ }): Promise<{
156
+ fileId: string;
157
+ name: string;
158
+ blobPath: string;
159
+ deduped: boolean;
160
+ }>;
161
+ importToWorkspace(ref: FileRef, opts?: {
162
+ folderPath?: string;
163
+ capBytes?: number;
164
+ }): Promise<{
165
+ fileId: string;
166
+ name: string;
167
+ blobPath: string;
168
+ deduped: boolean;
169
+ }>;
170
+ }
171
+ export interface PluginFilesCtx {
172
+ pluginId: string;
173
+ workspaceId: string;
174
+ sourceNodeId?: string;
175
+ applicationType?: string;
176
+ }
177
+ /**
178
+ * The consent-enforcing files API for plugin server code. Every call
179
+ * re-checks your manifest `fileSources` grant (explicit-only — no grant, no
180
+ * files); workspace writes re-check the kill switch; saves/imports append
181
+ * `workspace/file_added` with your plugin stamped as the actor. HOST-ONLY.
182
+ */
183
+ export declare function pluginFiles(_ctx: PluginFilesCtx): Promise<FilesApi>;
184
+ /** Bind the files API straight from an op/task context. HOST-ONLY. */
185
+ export declare function filesForOp(_ctx: {
186
+ pluginId: string;
187
+ workspaceId: string;
188
+ nodeId?: string;
189
+ }): Promise<FilesApi>;
package/dist/server.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Server half of a plugin package (`server.ts` in your plugin folder).
3
+ * Runs ONLY inside the ExternalSoul host (imported by the generic
4
+ * webhook/op routes) — it may use prisma, node crypto, provider SDKs.
5
+ *
6
+ * Outside the host these exports are typed stubs that throw with a clear
7
+ * message, so an author's unit tests fail loudly rather than mysteriously.
8
+ * Inside the host, `esoul-sdk/server` is aliased to the real
9
+ * implementations.
10
+ */
11
+ const hostOnly = (name) => {
12
+ throw new Error(`${name} runs only inside the ExternalSoul host (the app aliases esoul-sdk to its real implementations). In unit tests, mock this module.`);
13
+ };
14
+ /**
15
+ * Read the sealed credentials of a plugin-declared connection (auto-
16
+ * refreshes expiring OAuth tokens). HOST-ONLY.
17
+ */
18
+ export function getPluginConnectionCredentials(_connectionId, _pluginId) {
19
+ return hostOnly("getPluginConnectionCredentials");
20
+ }
21
+ /**
22
+ * Cross-app events: dispatch the TARGET app's own events through the
23
+ * platform spine (target's dataCreator mints; triggers fire; every event is
24
+ * actor-stamped `{kind:"plugin", pluginId, sourceNodeId}`). Same-workspace
25
+ * only. HOST-ONLY.
26
+ */
27
+ /**
28
+ * Read one app's state folded to head, by nodeId — server truth for an op or
29
+ * a task. Null when no such app exists. HOST-ONLY.
30
+ */
31
+ export function readAppState(_nodeId) {
32
+ return hostOnly("readAppState");
33
+ }
34
+ /**
35
+ * Call another app's tool from the server half (an op, task or webhook) —
36
+ * how an app orchestrates a my_computer or writes a spreadsheet from a job.
37
+ * Gated by the manifest's `workspaceTools` grants, same-workspace only.
38
+ * HOST-ONLY.
39
+ */
40
+ export function callWorkspaceTool(_a) {
41
+ return hostOnly("callWorkspaceTool");
42
+ }
43
+ export function emitPluginAppEvent(_args) {
44
+ return hostOnly("emitPluginAppEvent");
45
+ }
46
+ /**
47
+ * The consent-enforcing files API for plugin server code. Every call
48
+ * re-checks your manifest `fileSources` grant (explicit-only — no grant, no
49
+ * files); workspace writes re-check the kill switch; saves/imports append
50
+ * `workspace/file_added` with your plugin stamped as the actor. HOST-ONLY.
51
+ */
52
+ export function pluginFiles(_ctx) {
53
+ return hostOnly("pluginFiles");
54
+ }
55
+ /** Bind the files API straight from an op/task context. HOST-ONLY. */
56
+ export function filesForOp(_ctx) {
57
+ return hostOnly("filesForOp");
58
+ }
@@ -0,0 +1,20 @@
1
+ export interface MockOAuthServer {
2
+ port: number;
3
+ url: string;
4
+ close(): Promise<void>;
5
+ }
6
+ /**
7
+ * A real (tiny) OAuth2 provider for exercising the plugin-connection flow:
8
+ * /authorize (PKCE challenge bound to a one-time code), /token
9
+ * (authorization_code with verifier check + refresh_token grant), and a
10
+ * Bearer-gated /api/items resource. Access tokens expire fast (default 20s)
11
+ * so refresh paths are exercised without waiting.
12
+ *
13
+ * Point a manifest connection's authorizeUrl/tokenUrl at it and set the
14
+ * declared clientIdEnv to `clientId` below.
15
+ */
16
+ export declare function startMockOAuth(opts?: {
17
+ port?: number;
18
+ clientId?: string;
19
+ accessTtlMs?: number;
20
+ }): Promise<MockOAuthServer>;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Test utilities that need NOTHING from the host.
3
+ */
4
+ import http from "node:http";
5
+ import crypto from "node:crypto";
6
+ /**
7
+ * A real (tiny) OAuth2 provider for exercising the plugin-connection flow:
8
+ * /authorize (PKCE challenge bound to a one-time code), /token
9
+ * (authorization_code with verifier check + refresh_token grant), and a
10
+ * Bearer-gated /api/items resource. Access tokens expire fast (default 20s)
11
+ * so refresh paths are exercised without waiting.
12
+ *
13
+ * Point a manifest connection's authorizeUrl/tokenUrl at it and set the
14
+ * declared clientIdEnv to `clientId` below.
15
+ */
16
+ export function startMockOAuth(opts) {
17
+ const PORT = opts?.port ?? 39997;
18
+ const CLIENT_ID = opts?.clientId ?? "mock-client";
19
+ const TTL = opts?.accessTtlMs ?? 20_000;
20
+ const codes = new Map();
21
+ const accessTokens = new Map();
22
+ const refreshTokens = new Set();
23
+ const b64url = (buf) => Buffer.from(buf).toString("base64url");
24
+ const sha256 = (s) => crypto.createHash("sha256").update(s).digest();
25
+ const issueTokens = () => {
26
+ const access = `mock-access-${crypto.randomUUID()}`;
27
+ const refresh = `mock-refresh-${crypto.randomUUID()}`;
28
+ accessTokens.set(access, Date.now() + TTL);
29
+ refreshTokens.add(refresh);
30
+ const idToken = [
31
+ b64url(Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" }))),
32
+ b64url(Buffer.from(JSON.stringify({ sub: "mock-user-1" }))),
33
+ "",
34
+ ].join(".");
35
+ return {
36
+ access_token: access,
37
+ refresh_token: refresh,
38
+ expires_in: Math.floor(TTL / 1000),
39
+ token_type: "Bearer",
40
+ scope: "demo.read",
41
+ id_token: idToken,
42
+ };
43
+ };
44
+ const server = http.createServer(async (req, res) => {
45
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${PORT}`);
46
+ const json = (status, body) => {
47
+ res.writeHead(status, { "Content-Type": "application/json" });
48
+ res.end(JSON.stringify(body));
49
+ };
50
+ if (req.method === "GET" && url.pathname === "/authorize") {
51
+ const clientId = url.searchParams.get("client_id");
52
+ const redirectUri = url.searchParams.get("redirect_uri");
53
+ const state = url.searchParams.get("state") ?? "";
54
+ const challenge = url.searchParams.get("code_challenge");
55
+ if (clientId !== CLIENT_ID)
56
+ return json(400, { error: "bad client_id" });
57
+ if (!redirectUri || !challenge)
58
+ return json(400, { error: "missing params" });
59
+ const code = `mock-code-${crypto.randomUUID()}`;
60
+ codes.set(code, { challenge, redirectUri });
61
+ const back = new URL(redirectUri);
62
+ back.searchParams.set("code", code);
63
+ back.searchParams.set("state", state);
64
+ res.writeHead(302, { Location: back.toString() });
65
+ return res.end();
66
+ }
67
+ if (req.method === "POST" && url.pathname === "/token") {
68
+ let raw = "";
69
+ for await (const chunk of req)
70
+ raw += chunk;
71
+ const p = new URLSearchParams(raw);
72
+ if (p.get("client_id") && p.get("client_id") !== CLIENT_ID) {
73
+ return json(400, { error: "bad client_id" });
74
+ }
75
+ const grant = p.get("grant_type");
76
+ if (grant === "authorization_code") {
77
+ const entry = codes.get(p.get("code") ?? "");
78
+ if (!entry)
79
+ return json(400, { error: "bad code" });
80
+ codes.delete(p.get("code"));
81
+ if (b64url(sha256(p.get("code_verifier") ?? "")) !== entry.challenge) {
82
+ return json(400, { error: "pkce verification failed" });
83
+ }
84
+ if (p.get("redirect_uri") !== entry.redirectUri) {
85
+ return json(400, { error: "redirect_uri mismatch" });
86
+ }
87
+ return json(200, issueTokens());
88
+ }
89
+ if (grant === "refresh_token") {
90
+ if (!refreshTokens.has(p.get("refresh_token") ?? "")) {
91
+ return json(400, { error: "bad refresh_token" });
92
+ }
93
+ return json(200, issueTokens());
94
+ }
95
+ return json(400, { error: "unsupported grant_type" });
96
+ }
97
+ if (req.method === "GET" && url.pathname === "/api/items") {
98
+ const token = (req.headers.authorization ?? "").replace(/^Bearer\s+/i, "");
99
+ const exp = accessTokens.get(token);
100
+ if (!exp)
101
+ return json(401, { error: "unknown token" });
102
+ if (exp < Date.now())
103
+ return json(401, { error: "token expired" });
104
+ return json(200, {
105
+ items: [
106
+ { id: "m1", text: "row one from the mock provider" },
107
+ { id: "m2", text: "row two from the mock provider" },
108
+ ],
109
+ });
110
+ }
111
+ json(404, { error: "not found" });
112
+ });
113
+ return new Promise((resolve, reject) => {
114
+ server.once("error", reject);
115
+ server.listen(PORT, "127.0.0.1", () => resolve({
116
+ port: PORT,
117
+ url: `http://127.0.0.1:${PORT}`,
118
+ close: () => new Promise((r) => server.close(() => r())),
119
+ }));
120
+ });
121
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * The plugin contract — mirrored from the ExternalSoul platform.
3
+ *
4
+ * Inside the host repo, `esoul-sdk` is aliased to the real
5
+ * platform modules, so these mirrors are used only OUT of the repo (editor
6
+ * IntelliSense, `tsc` in an author's folder, the validator CLI). A drift
7
+ * gate in the host asserts assignability between these and the platform's
8
+ * own types on every build — the mirror cannot silently rot.
9
+ */
10
+ import type { ComponentType } from "react";
11
+ import type { z } from "zod";
12
+ /** Identity every app state extends — set by the platform, never by you. */
13
+ /**
14
+ * MIRRORS the host's `ApplicationIdentifier` (venus-sdk.ts) exactly.
15
+ *
16
+ * It used to carry `[extra: string]: unknown`, meaning to say "your state may
17
+ * add fields". That broke the drift gate's FORWARD assertion in a way no error
18
+ * message explains: TypeScript never infers an implicit index signature for an
19
+ * interface, so the HOST identifier was not assignable to this one, and every
20
+ * schema member taking an identifier (`toolkitCreator`, `getPorts`, `events`,
21
+ * `publicSharing`) failed contravariantly. Extra fields need no index
22
+ * signature — a plugin declares them the way the reference packages do:
23
+ * `export interface PluginTodoData extends ApplicationIdentifier { items: … }`
24
+ */
25
+ export interface ApplicationIdentifier {
26
+ workspaceId: string;
27
+ nodeId: string;
28
+ applicationType: string;
29
+ instanceName: string;
30
+ /**
31
+ * Set ONLY by the MCP storefront's `executeAppTool`: the external caller a
32
+ * tool runs for. Chat / agent / voice toolkits never set it.
33
+ */
34
+ externalActor?: {
35
+ kind?: "external" | "sandbox";
36
+ userId: string;
37
+ email: string | null;
38
+ handle: string | null;
39
+ viaProfileId: string;
40
+ sessionId: string;
41
+ anonymous?: boolean;
42
+ };
43
+ }
44
+ export declare enum EventTypes {
45
+ Client = "Client",
46
+ Workflow = "Workflow",
47
+ Workspace = "Workspace"
48
+ }
49
+ export interface EventData<T = unknown> {
50
+ eventName: string;
51
+ workspaceId: string;
52
+ applicationId: string | undefined;
53
+ instanceName: string | undefined;
54
+ /** REQUIRED-but-maybe-undefined, exactly as the host declares it. An
55
+ * OPTIONAL `chatIdSource?: string` is not assignable to the host's
56
+ * required member, which silently broke the drift gate's forward
57
+ * assertion for every event definition. */
58
+ chatIdSource: string | undefined;
59
+ eventData: T;
60
+ timestamp: number;
61
+ }
62
+ export interface CollapsibleConfig {
63
+ /** Same key within the window ⇒ events merge (newest content wins). */
64
+ collapseKeyFn: (eventData: any, context: EventData<any>) => string;
65
+ collapseWindowMs: number;
66
+ }
67
+ export interface TriggerMeta {
68
+ displayName?: string;
69
+ description?: string;
70
+ sampleVariables?: string[];
71
+ payloadShape?: "single" | "batch";
72
+ batchAccessor?: string;
73
+ }
74
+ export type EventReversibility = "pure" | "external-reversible" | "external-irreversible";
75
+ /**
76
+ * MIRRORS the host (event-spec.ts). This mirror used to declare
77
+ * `EventSideEffect` as the bare string union above, so an author writing
78
+ * `sideEffect: "external-irreversible"` type-checked against the SDK and was
79
+ * rejected by the platform — the drift gate exists for exactly this.
80
+ *
81
+ * The scrub/revert kernel reads it: `reversibility` says whether undoing the
82
+ * event can undo its effect on the outside world, and an `inverseEventName` +
83
+ * `buildInverseEventData` let the platform actually compensate.
84
+ */
85
+ export interface EventSideEffect {
86
+ reversibility: EventReversibility;
87
+ description?: string;
88
+ inverseEventName?: string;
89
+ buildInverseEventData?: (forwardEventData: any, parentEvent: EventData<any>) => any;
90
+ criticality?: "status" | "normal";
91
+ }
92
+ export interface EventDefinition<StateType extends ApplicationIdentifier> {
93
+ eventName: string;
94
+ type: EventTypes;
95
+ /** Mints the full envelope. IDS AND TIMESTAMPS ARE MINTED HERE — never in
96
+ * the processor (replay determinism). */
97
+ dataCreator: (params: Record<string, any>) => EventData<any>;
98
+ /** Pure fold step: (state, event) → state. Idempotent by id; no
99
+ * Date.now/random; malformed data returns state unchanged, never throws. */
100
+ processor: (state: StateType, eventData: any) => StateType;
101
+ collapseConfig?: CollapsibleConfig;
102
+ triggerMeta?: TriggerMeta;
103
+ sideEffect?: EventSideEffect;
104
+ permission?: "user_exclusive";
105
+ conflictPolicy?: "mark" | "silent";
106
+ }
107
+ /**
108
+ * What a tool may return. MIRRORS the host's `ToolResult`
109
+ * (src/application-interfaces/venus-sdk.ts) exactly — the drift gate
110
+ * (src/lib/plugins/sdk-drift.test.ts) asserts a plugin authored against this
111
+ * package satisfies the platform, and this type used to be `unknown`: wider
112
+ * than the host's union, so `Promise<unknown>` was not assignable to
113
+ * `Promise<ToolResult>` and EVERY SDK-authored toolkit failed the forward
114
+ * assertion. A plugin author must learn the real contract here, not at
115
+ * install time.
116
+ *
117
+ * `{ text, images? }` is how a tool returns pictures the model can see;
118
+ * `imageUrls` are workspace blob URLs.
119
+ */
120
+ export type ToolResult = string | {
121
+ text: string;
122
+ images?: string[];
123
+ imageUrls?: string[];
124
+ };
125
+ export interface ApplicationTools {
126
+ description: string;
127
+ parameters: z.ZodObject<Record<string, z.ZodTypeAny>>;
128
+ /** Server-side execution. */
129
+ execute: (args: Record<string, any>) => Promise<ToolResult>;
130
+ /** Browser-side execution (WebRTC voice etc.). REQUIRED — a tool without
131
+ * it type-fails the whole toolkit. */
132
+ onClient: (args: Record<string, any>) => void | ToolResult | Promise<void | ToolResult>;
133
+ background?: boolean;
134
+ realtimeWait?: {
135
+ channel: any;
136
+ topic: string;
137
+ isTerminal: (data: any) => boolean;
138
+ extractResult: (data: any) => string;
139
+ };
140
+ }
141
+ /** Durable-task context (Inngest under the hood). Everything you need is on
142
+ * ctx — a task handler imports NOTHING server-only. */
143
+ export interface AppTaskContext<TState extends ApplicationIdentifier> {
144
+ identifier: ApplicationIdentifier;
145
+ eventData: Record<string, any>;
146
+ /** Inngest step API. RULE: every side effect goes inside a step.run. */
147
+ step: any;
148
+ logger: any;
149
+ getState(): Promise<TState>;
150
+ dispatchEvent(eventName: string, eventData: any): Promise<void>;
151
+ notify(topic: string, data: any): Promise<void>;
152
+ }
153
+ export interface AppTaskDefinition<TState extends ApplicationIdentifier> {
154
+ /** Becomes the Inngest event `<applicationType>/<taskName>`. */
155
+ taskName: string;
156
+ description?: string;
157
+ concurrency?: {
158
+ limit: number;
159
+ scope: "per-app" | "global";
160
+ };
161
+ handler: (ctx: AppTaskContext<TState>) => Promise<void>;
162
+ }
163
+ /** MIRRORS the host. What a redactor is told about who is looking. */
164
+ export interface PublicSharingContext {
165
+ viewerKind: "public" | "collaborator-readonly";
166
+ defaultRole: string;
167
+ appRoles: Record<string, string> | null;
168
+ /** Every id this viewer has acted under (guest cookie, user id, guest ids
169
+ * linked at login) — keep their own items unredacted, blank the rest. */
170
+ viewerIds?: string[];
171
+ }
172
+ export interface PublicSharingPolicy<StateType> {
173
+ policy?: "allowed" | "never";
174
+ /** Returns the FULL state shape, not a Partial — the host folds the result
175
+ * as the app's state. (This mirror said `Partial<StateType>`, which is not
176
+ * assignable to the host's return type: a plugin author following the SDK
177
+ * would have written a redactor the platform rejects.) */
178
+ redactState?: (state: StateType, ctx: PublicSharingContext) => StateType;
179
+ }
180
+ /** MIRRORS the host's `ApplicationPort` (model/knowledge-workspace.ts). The
181
+ * two loosely-typed members are platform-internal shapes a plugin never
182
+ * constructs by hand; most plugins return `[]` from `getPorts`. */
183
+ export interface ApplicationPort {
184
+ id: string;
185
+ portType: any;
186
+ portName: string;
187
+ eventName: string;
188
+ eventDataSchema: any;
189
+ eventTargets: any[];
190
+ }
191
+ /** MIRRORS the host. Present only when a tool runs inside an agent-builder
192
+ * run; absent for chat / voice / SDK calls. */
193
+ export interface AgentRunToolContext {
194
+ agentRunId: string;
195
+ agentBuilderNodeId: string;
196
+ currentAgentNodeId: string | null;
197
+ userId: string;
198
+ setWaitDirectiveCreatedThisStep: () => void;
199
+ isWaitDirectiveAlreadyCreatedThisStep: () => boolean;
200
+ }
201
+ /**
202
+ * THE schema — the one object a plugin's entry module exports as
203
+ * `pluginSchema`. Identical contract to every built-in app.
204
+ */
205
+ export interface ApplicationSchema<StateType extends ApplicationIdentifier> {
206
+ applicationType: string;
207
+ description: string;
208
+ controlPlane?: true;
209
+ reactNode: ComponentType<{
210
+ state: StateType;
211
+ }>;
212
+ events: EventDefinition<StateType>[];
213
+ stateCreator: (identifier: ApplicationIdentifier, data: any) => StateType;
214
+ toolkitCreator: (identifier: ApplicationIdentifier, forChatId: string, eventCallback: (event: any) => void, chatMessageCallback: (message: any) => void, runCtx?: AgentRunToolContext) => Record<string, ApplicationTools>;
215
+ getPorts: () => ApplicationPort[];
216
+ tasks?: AppTaskDefinition<StateType>[];
217
+ channel?: (params: {
218
+ workspaceId: string;
219
+ nodeId: string;
220
+ }) => any;
221
+ getStateDescription: (applicationState: any) => string;
222
+ publicSharing?: PublicSharingPolicy<StateType>;
223
+ movable?: boolean;
224
+ reconstructStateFromEventLog?: boolean;
225
+ slimForBroadcast?: (state: StateType) => Partial<StateType> | undefined;
226
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ export var EventTypes;
2
+ (function (EventTypes) {
3
+ EventTypes["Client"] = "Client";
4
+ EventTypes["Workflow"] = "Workflow";
5
+ EventTypes["Workspace"] = "Workspace";
6
+ })(EventTypes || (EventTypes = {}));