@jetcrab/pi-desk-sdk 0.0.0-stage → 1.0.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,167 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { normalizePluginIdentifier } from "./shared.js";
3
+ export { PluginMethodError, isPluginMethodError } from "./errors.js";
4
+ export const SESSION_PLUGIN_STATE_EVENT = 'pi-desk:session-plugins';
5
+ export const SESSION_PLUGIN_METHOD_EVENT = 'pi-desk:session-plugin-methods:v1';
6
+ export const SESSION_PLUGIN_PUSH_EVENT = 'pi-desk:session-plugin-push:v1';
7
+ export const SESSION_PLUGIN_CAPABILITIES_EVENT = 'pi-desk:session-capabilities';
8
+ export function bindSessionPlugin(pi, pluginName) {
9
+ const name = normalizePluginIdentifier(pluginName, 'pluginName');
10
+ return {
11
+ name,
12
+ setState(state) {
13
+ pi.events.emit(SESSION_PLUGIN_STATE_EVENT, { [name]: state });
14
+ },
15
+ push(eventInput, data) {
16
+ const event = normalizePluginIdentifier(eventInput, 'event');
17
+ pi.events.emit(SESSION_PLUGIN_PUSH_EVENT, {
18
+ pluginName: name,
19
+ event,
20
+ data
21
+ });
22
+ },
23
+ registerMethod(methodInput, execute) {
24
+ const method = normalizePluginIdentifier(methodInput, 'method');
25
+ const registration = {
26
+ pluginName: name,
27
+ method,
28
+ execute
29
+ };
30
+ pi.events.emit(SESSION_PLUGIN_METHOD_EVENT, { type: 'register', registration });
31
+ let registered = true;
32
+ return () => {
33
+ if (!registered)
34
+ return;
35
+ registered = false;
36
+ pi.events.emit(SESSION_PLUGIN_METHOD_EVENT, { type: 'unregister', registration });
37
+ };
38
+ },
39
+ onCapabilitiesChange(execute) {
40
+ const registration = {
41
+ pluginName: name,
42
+ execute,
43
+ hostManaged: false
44
+ };
45
+ let active = true;
46
+ pi.events.emit(SESSION_PLUGIN_CAPABILITIES_EVENT, { type: 'register', registration });
47
+ const unregister = () => {
48
+ if (!active)
49
+ return;
50
+ active = false;
51
+ pi.events.emit(SESSION_PLUGIN_CAPABILITIES_EVENT, { type: 'unregister', registration });
52
+ };
53
+ pi.on('session_start', async (_event, context) => {
54
+ if (active && !registration.hostManaged) {
55
+ await execute({ tools: {}, skills: {}, capabilities: {} }, context);
56
+ }
57
+ });
58
+ pi.on('session_shutdown', unregister);
59
+ return unregister;
60
+ },
61
+ startTask(input) {
62
+ return startTask(pi, input);
63
+ },
64
+ reportTasks(tasks) {
65
+ reportTasks(pi, tasks);
66
+ }
67
+ };
68
+ }
69
+ export const TASK_REPORT_EVENT = 'pi-desk:task-report:v1';
70
+ function timestamp() {
71
+ return Date.now();
72
+ }
73
+ function terminalTask(snapshot, status, activity) {
74
+ const source = snapshot.detailSource;
75
+ const session = source?.kind === 'pi-conversation' ? source.session : undefined;
76
+ const detailSource = session?.sessionFile
77
+ ? { kind: 'pi-conversation', sessionFile: session.sessionFile }
78
+ : source;
79
+ return {
80
+ ...snapshot,
81
+ status,
82
+ activity: activity === undefined ? snapshot.activity : activity,
83
+ endedAt: timestamp(),
84
+ detailSource,
85
+ interrupt: null
86
+ };
87
+ }
88
+ function emit(target, event) {
89
+ target.events.emit(TASK_REPORT_EVENT, event);
90
+ }
91
+ export function reportTasks(target, tasks) {
92
+ emit(target, { type: 'upsert', tasks });
93
+ }
94
+ export function startTask(target, input) {
95
+ let snapshot = {
96
+ taskId: input.taskId ?? randomUUID(),
97
+ taskKind: input.taskKind ?? null,
98
+ taskType: input.taskType ?? null,
99
+ title: input.title,
100
+ info: input.info?.map((item) => ({ ...item })) ?? [],
101
+ ...(input.infoLoader ? { infoLoader: input.infoLoader } : {}),
102
+ status: 'running',
103
+ activity: input.activity ?? null,
104
+ startedAt: input.startedAt ?? timestamp(),
105
+ endedAt: null,
106
+ detailSource: input.detailSource ?? null,
107
+ interrupt: input.interrupt ?? null
108
+ };
109
+ let removed = false;
110
+ const report = () => {
111
+ if (removed)
112
+ throw new Error('Task has been removed');
113
+ emit(target, { type: 'upsert', tasks: [snapshot] });
114
+ };
115
+ report();
116
+ return {
117
+ get taskId() {
118
+ return snapshot.taskId;
119
+ },
120
+ update(input) {
121
+ if (snapshot.status !== 'running')
122
+ throw new Error('Terminal task cannot be updated');
123
+ snapshot = {
124
+ ...snapshot,
125
+ ...input,
126
+ taskKind: input.taskKind === undefined ? snapshot.taskKind : input.taskKind,
127
+ taskType: input.taskType === undefined ? snapshot.taskType : input.taskType,
128
+ info: input.info === undefined ? snapshot.info : input.info.map((item) => ({ ...item })),
129
+ infoLoader: input.infoLoader === undefined ? snapshot.infoLoader : input.infoLoader,
130
+ activity: input.activity === undefined ? snapshot.activity : input.activity,
131
+ detailSource: input.detailSource === undefined ? snapshot.detailSource : input.detailSource,
132
+ interrupt: input.interrupt === undefined ? snapshot.interrupt : input.interrupt
133
+ };
134
+ report();
135
+ },
136
+ complete(activity) {
137
+ if (snapshot.status !== 'running')
138
+ throw new Error('Task is already terminal');
139
+ snapshot = terminalTask(snapshot, 'completed', activity);
140
+ report();
141
+ },
142
+ fail(activity) {
143
+ if (snapshot.status !== 'running')
144
+ throw new Error('Task is already terminal');
145
+ snapshot = terminalTask(snapshot, 'failed', activity);
146
+ report();
147
+ },
148
+ stopped(activity) {
149
+ if (snapshot.status !== 'running')
150
+ throw new Error('Task is already terminal');
151
+ snapshot = terminalTask(snapshot, 'stopped', activity);
152
+ report();
153
+ },
154
+ interrupted(activity) {
155
+ if (snapshot.status !== 'running')
156
+ throw new Error('Task is already terminal');
157
+ snapshot = terminalTask(snapshot, 'interrupted', activity);
158
+ report();
159
+ },
160
+ remove() {
161
+ if (removed)
162
+ return;
163
+ removed = true;
164
+ emit(target, { type: 'remove', taskIds: [snapshot.taskId] });
165
+ }
166
+ };
167
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import type { PluginDisposer } from './shared.js';
3
+ export declare const HostRegionSchema: z.ZodReadonly<z.ZodObject<{
4
+ locale: z.ZodEnum<{
5
+ "zh-CN": "zh-CN";
6
+ en: "en";
7
+ }>;
8
+ timeZone: z.ZodString;
9
+ }, z.core.$strict>>;
10
+ export declare const HostSettingsSchema: z.ZodReadonly<z.ZodObject<{
11
+ region: z.ZodReadonly<z.ZodObject<{
12
+ locale: z.ZodEnum<{
13
+ "zh-CN": "zh-CN";
14
+ en: "en";
15
+ }>;
16
+ timeZone: z.ZodString;
17
+ }, z.core.$strict>>;
18
+ }, z.core.$strict>>;
19
+ export type HostRegion = z.infer<typeof HostRegionSchema>;
20
+ export type HostSettingsSnapshot = z.infer<typeof HostSettingsSchema>;
21
+ export interface HostSettings {
22
+ getSnapshot(): HostSettingsSnapshot;
23
+ subscribe(listener: () => void): PluginDisposer;
24
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ export const HostRegionSchema = z
3
+ .object({
4
+ locale: z.enum(['zh-CN', 'en']),
5
+ timeZone: z
6
+ .string()
7
+ .trim()
8
+ .min(1)
9
+ .max(100)
10
+ .refine((value) => {
11
+ try {
12
+ new Intl.DateTimeFormat('en', { timeZone: value });
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }, '无效的 IANA 时区')
19
+ })
20
+ .strict()
21
+ .readonly();
22
+ export const HostSettingsSchema = z.object({ region: HostRegionSchema }).strict().readonly();
@@ -0,0 +1,31 @@
1
+ export type PluginJsonValue = null | boolean | number | string | PluginJsonValue[] | {
2
+ [key: string]: PluginJsonValue;
3
+ };
4
+ export type PluginJsonObject = {
5
+ [key: string]: PluginJsonValue;
6
+ };
7
+ export type PluginDisposer = () => void | Promise<void>;
8
+ export type PluginNotificationLevel = 'info' | 'success' | 'warning' | 'error';
9
+ export interface PluginSource {
10
+ readonly workId: string;
11
+ readonly sessionId: string;
12
+ readonly branchId: string;
13
+ }
14
+ export interface PluginWorkSession {
15
+ readonly source: PluginSource;
16
+ readonly cwd: string;
17
+ readonly status: 'main_running' | 'background_running' | 'completed' | 'idle';
18
+ }
19
+ export interface BrowserApplicationContext {
20
+ source: PluginSource;
21
+ cwd: string;
22
+ }
23
+ export type BrowserBuiltinIconName = 'plugin' | 'server' | 'rocket' | 'settings' | 'panel' | 'message' | 'terminal' | 'file' | 'folder' | 'bot' | 'wrench' | 'database' | 'globe';
24
+ export type BrowserContributionIcon = {
25
+ type: 'builtin';
26
+ name: BrowserBuiltinIconName;
27
+ } | {
28
+ type: 'svg';
29
+ content: string;
30
+ };
31
+ export declare function normalizePluginIdentifier(value: string, field: 'pluginName' | 'method' | 'contributionName' | 'declarationName' | 'event'): string;
package/dist/shared.js ADDED
@@ -0,0 +1,11 @@
1
+ const PLUGIN_IDENTIFIER_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2
+ const PLUGIN_IDENTIFIER_MAX_LENGTH = 64;
3
+ export function normalizePluginIdentifier(value, field) {
4
+ const normalized = value.trim();
5
+ if (normalized.length === 0 ||
6
+ normalized.length > PLUGIN_IDENTIFIER_MAX_LENGTH ||
7
+ !PLUGIN_IDENTIFIER_PATTERN.test(normalized)) {
8
+ throw new Error(`${field} must use lowercase kebab-case and contain 1-64 characters`);
9
+ }
10
+ return normalized;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,121 @@
1
1
  {
2
2
  "name": "@jetcrab/pi-desk-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/JetCrab/pi-desk.git",
6
+ "directory": "plugins/pi-desk-sdk"
7
+ },
8
+ "version": "1.0.0",
9
+ "license": "Apache-2.0",
10
+ "publishConfig": {
11
+ "registry": "https://registry.npmjs.org",
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./settings": {
22
+ "types": "./dist/settings.d.ts",
23
+ "import": "./dist/settings.js",
24
+ "default": "./dist/settings.js"
25
+ },
26
+ "./errors": {
27
+ "types": "./dist/errors.d.ts",
28
+ "import": "./dist/errors.js",
29
+ "default": "./dist/errors.js"
30
+ },
31
+ "./capabilities": {
32
+ "types": "./dist/capabilities.d.ts",
33
+ "import": "./dist/capabilities.js",
34
+ "default": "./dist/capabilities.js"
35
+ },
36
+ "./entry": {
37
+ "types": "./dist/entry.d.ts",
38
+ "import": "./dist/entry.js",
39
+ "default": "./dist/entry.js"
40
+ },
41
+ "./session": {
42
+ "types": "./dist/session.d.ts",
43
+ "import": "./dist/session.js",
44
+ "default": "./dist/session.js"
45
+ },
46
+ "./browser": {
47
+ "types": "./dist/browser.d.ts",
48
+ "import": "./dist/browser.js",
49
+ "default": "./dist/browser.js"
50
+ },
51
+ "./react": {
52
+ "types": "./dist/react.d.ts",
53
+ "import": "./dist/react.js",
54
+ "default": "./dist/react.js"
55
+ },
56
+ "./react/base": {
57
+ "types": "./dist/react-base.d.ts",
58
+ "import": "./dist/react-base.js",
59
+ "default": "./dist/react-base.js"
60
+ },
61
+ "./react/markdown": {
62
+ "types": "./dist/react-markdown.d.ts",
63
+ "import": "./dist/react-markdown.js",
64
+ "default": "./dist/react-markdown.js"
65
+ },
66
+ "./host-runtime/*": "./dist/host-runtime/*"
67
+ },
68
+ "types": "./dist/index.d.ts",
69
+ "files": [
70
+ "dist",
71
+ "README.md"
72
+ ],
73
+ "dependencies": {
74
+ "zod": "^4.4.3"
75
+ },
76
+ "peerDependencies": {
77
+ "@base-ui/react": "^1.7.0",
78
+ "@earendil-works/pi-coding-agent": "*",
79
+ "@streamdown/cjk": "^1.0.3",
80
+ "lucide-react": "^1.31.0",
81
+ "react": ">=19",
82
+ "react-dom": ">=19",
83
+ "streamdown": "^2.5.0"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "@base-ui/react": {
87
+ "optional": true
88
+ },
89
+ "@earendil-works/pi-coding-agent": {
90
+ "optional": true
91
+ },
92
+ "@streamdown/cjk": {
93
+ "optional": true
94
+ },
95
+ "lucide-react": {
96
+ "optional": true
97
+ },
98
+ "react": {
99
+ "optional": true
100
+ },
101
+ "react-dom": {
102
+ "optional": true
103
+ },
104
+ "streamdown": {
105
+ "optional": true
106
+ }
107
+ },
108
+ "devDependencies": {
109
+ "@base-ui/react": "^1.7.0",
110
+ "@streamdown/cjk": "^1.0.3",
111
+ "esbuild": "^0.28.2",
112
+ "lucide-react": "^1.31.0",
113
+ "react": "19.2.8",
114
+ "react-dom": "19.2.8",
115
+ "streamdown": "^2.5.0"
116
+ },
117
+ "scripts": {
118
+ "build": "tsc -p tsconfig.json && node scripts/build-host-runtime.mjs",
119
+ "test": "pnpm run build && node --test tests/*.test.mjs"
120
+ }
6
121
  }