@parall/daemon 1.30.0 → 1.32.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/bundle/manifest.json +17 -11
- package/bundle/package.json +1 -0
- package/bundle/parall-claude-agent.js +27671 -337
- package/bundle/parall-codex-agent.js +27715 -375
- package/bundle/parall-daemon.js +30421 -1355
- package/bundle/parall-openclaw-agent.js +51 -26
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +122 -72
- package/dist/clip-runtime/clip-installer.d.ts +44 -0
- package/dist/clip-runtime/clip-installer.d.ts.map +1 -0
- package/dist/clip-runtime/clip-installer.js +501 -0
- package/dist/clip-runtime/clip-provider.d.ts +76 -0
- package/dist/clip-runtime/clip-provider.d.ts.map +1 -0
- package/dist/clip-runtime/clip-provider.js +402 -0
- package/dist/clip-runtime/index.d.ts +7 -0
- package/dist/clip-runtime/index.d.ts.map +1 -0
- package/dist/clip-runtime/index.js +5 -0
- package/dist/clip-runtime/ipc.d.ts +94 -0
- package/dist/clip-runtime/ipc.d.ts.map +1 -0
- package/dist/clip-runtime/ipc.js +98 -0
- package/dist/clip-runtime/manifest.d.ts +74 -0
- package/dist/clip-runtime/manifest.d.ts.map +1 -0
- package/dist/clip-runtime/manifest.js +181 -0
- package/dist/clip-runtime/process-manager.d.ts +57 -0
- package/dist/clip-runtime/process-manager.d.ts.map +1 -0
- package/dist/clip-runtime/process-manager.js +354 -0
- package/dist/clip-runtime/process.d.ts +59 -0
- package/dist/clip-runtime/process.d.ts.map +1 -0
- package/dist/clip-runtime/process.js +350 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -19
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +51 -53
- package/dist/index.js +46 -14
- package/dist/runtimes.d.ts +10 -8
- package/dist/runtimes.d.ts.map +1 -1
- package/dist/runtimes.js +63 -95
- package/dist/supervisor.d.ts +12 -3
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +272 -71
- package/dist/updater-manifest.d.ts +41 -0
- package/dist/updater-manifest.d.ts.map +1 -0
- package/dist/updater-manifest.js +94 -0
- package/dist/updater.d.ts +60 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +427 -0
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +112 -112
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAQzD;AAYD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAkBrE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CA+C/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAoBvF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,aAAa,CAevE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export function loadClipJson(dir) {
|
|
4
|
+
const filePath = path.join(dir, 'clip.json');
|
|
5
|
+
try {
|
|
6
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
7
|
+
return JSON.parse(content);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function loadPackageJson(dir) {
|
|
14
|
+
const filePath = path.join(dir, 'package.json');
|
|
15
|
+
try {
|
|
16
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function loadProjectMetadata(clip) {
|
|
24
|
+
const meta = {};
|
|
25
|
+
const clipJson = loadClipJson(clip.path);
|
|
26
|
+
if (clipJson) {
|
|
27
|
+
meta.package = clipJson.name;
|
|
28
|
+
meta.version = clipJson.version;
|
|
29
|
+
meta.description = clipJson.description;
|
|
30
|
+
meta.main = clipJson.main;
|
|
31
|
+
meta.web = clipJson.web;
|
|
32
|
+
}
|
|
33
|
+
const pkgJson = loadPackageJson(clip.path);
|
|
34
|
+
if (pkgJson) {
|
|
35
|
+
if (!meta.package)
|
|
36
|
+
meta.package = pkgJson.name;
|
|
37
|
+
if (!meta.version)
|
|
38
|
+
meta.version = pkgJson.version;
|
|
39
|
+
if (!meta.description)
|
|
40
|
+
meta.description = pkgJson.description;
|
|
41
|
+
if (!meta.main)
|
|
42
|
+
meta.main = pkgJson.main;
|
|
43
|
+
}
|
|
44
|
+
return meta;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse IPC commands in any of the three Pinix-supported formats:
|
|
48
|
+
* 1. string[] → ["add", "remove"]
|
|
49
|
+
* 2. CommandDetail[] → [{ name: "add", description: "..." }]
|
|
50
|
+
* 3. Record<string, object> → { "add": { description: "..." } }
|
|
51
|
+
*/
|
|
52
|
+
export function parseIpcCommands(data) {
|
|
53
|
+
if (!data)
|
|
54
|
+
return [];
|
|
55
|
+
if (Array.isArray(data)) {
|
|
56
|
+
if (data.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
if (typeof data[0] === 'string') {
|
|
59
|
+
return data.map((name) => ({ name: name.trim() }));
|
|
60
|
+
}
|
|
61
|
+
return data
|
|
62
|
+
.map((item) => {
|
|
63
|
+
const name = (item.name ?? item.command ?? item.id ?? '');
|
|
64
|
+
return {
|
|
65
|
+
name: String(name).trim(),
|
|
66
|
+
description: item.description ? String(item.description).trim() : undefined,
|
|
67
|
+
input: item.input
|
|
68
|
+
? typeof item.input === 'string'
|
|
69
|
+
? item.input
|
|
70
|
+
: JSON.stringify(item.input)
|
|
71
|
+
: undefined,
|
|
72
|
+
output: item.output
|
|
73
|
+
? typeof item.output === 'string'
|
|
74
|
+
? item.output
|
|
75
|
+
: JSON.stringify(item.output)
|
|
76
|
+
: undefined,
|
|
77
|
+
};
|
|
78
|
+
})
|
|
79
|
+
.filter((c) => c.name);
|
|
80
|
+
}
|
|
81
|
+
if (typeof data === 'object' && data !== null) {
|
|
82
|
+
const record = data;
|
|
83
|
+
return Object.entries(record)
|
|
84
|
+
.map(([name, details]) => ({
|
|
85
|
+
name: name.trim(),
|
|
86
|
+
description: details?.description ? String(details.description).trim() : undefined,
|
|
87
|
+
input: details?.input
|
|
88
|
+
? typeof details.input === 'string'
|
|
89
|
+
? details.input
|
|
90
|
+
: JSON.stringify(details.input)
|
|
91
|
+
: undefined,
|
|
92
|
+
output: details?.output
|
|
93
|
+
? typeof details.output === 'string'
|
|
94
|
+
? details.output
|
|
95
|
+
: JSON.stringify(details.output)
|
|
96
|
+
: undefined,
|
|
97
|
+
}))
|
|
98
|
+
.filter((c) => c.name);
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
export function enrichManifest(clip, manifest) {
|
|
103
|
+
const meta = loadProjectMetadata(clip);
|
|
104
|
+
const enriched = { ...manifest };
|
|
105
|
+
if (!enriched.name)
|
|
106
|
+
enriched.name = clip.name;
|
|
107
|
+
if (!enriched.package && meta.package)
|
|
108
|
+
enriched.package = meta.package;
|
|
109
|
+
if (!enriched.version && meta.version)
|
|
110
|
+
enriched.version = meta.version;
|
|
111
|
+
if (!enriched.description && meta.description)
|
|
112
|
+
enriched.description = meta.description;
|
|
113
|
+
if (!enriched.domain && meta.domain)
|
|
114
|
+
enriched.domain = meta.domain;
|
|
115
|
+
if (meta.commands && meta.commands.length > 0 && enriched.commandDetails.length === 0) {
|
|
116
|
+
enriched.commandDetails = meta.commands;
|
|
117
|
+
enriched.commands = meta.commands.map((c) => c.name);
|
|
118
|
+
}
|
|
119
|
+
if (enriched.hasWeb === undefined && meta.web) {
|
|
120
|
+
enriched.hasWeb = true;
|
|
121
|
+
}
|
|
122
|
+
return finalizeManifest(enriched);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a ManifestCache from IPC register message manifest data.
|
|
126
|
+
*/
|
|
127
|
+
export function manifestFromIpc(ipcManifest) {
|
|
128
|
+
const details = parseIpcCommands(ipcManifest.commands);
|
|
129
|
+
return {
|
|
130
|
+
name: '',
|
|
131
|
+
package: ipcManifest.package,
|
|
132
|
+
version: ipcManifest.version,
|
|
133
|
+
domain: ipcManifest.domain,
|
|
134
|
+
description: ipcManifest.description,
|
|
135
|
+
commands: details.map((c) => c.name),
|
|
136
|
+
commandDetails: details,
|
|
137
|
+
hasWeb: ipcManifest.has_web,
|
|
138
|
+
dependencies: ipcManifest.dependencies,
|
|
139
|
+
patterns: ipcManifest.patterns,
|
|
140
|
+
entities: ipcManifest.entities,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function resolveEntrypoint(clip) {
|
|
144
|
+
const clipJson = loadClipJson(clip.path);
|
|
145
|
+
if (clipJson?.main)
|
|
146
|
+
return path.join(clip.path, clipJson.main);
|
|
147
|
+
const pkgJson = loadPackageJson(clip.path);
|
|
148
|
+
if (pkgJson?.main)
|
|
149
|
+
return path.join(clip.path, pkgJson.main);
|
|
150
|
+
if (pkgJson?.bin) {
|
|
151
|
+
const binPath = typeof pkgJson.bin === 'string' ? pkgJson.bin : Object.values(pkgJson.bin)[0];
|
|
152
|
+
if (binPath)
|
|
153
|
+
return path.join(clip.path, binPath);
|
|
154
|
+
}
|
|
155
|
+
const defaultEntry = path.join(clip.path, 'index.ts');
|
|
156
|
+
if (fs.existsSync(defaultEntry))
|
|
157
|
+
return defaultEntry;
|
|
158
|
+
return path.join(clip.path, 'index.js');
|
|
159
|
+
}
|
|
160
|
+
function finalizeManifest(manifest) {
|
|
161
|
+
const m = { ...manifest };
|
|
162
|
+
m.name = (m.name ?? '').trim();
|
|
163
|
+
m.package = m.package?.trim();
|
|
164
|
+
m.version = m.version?.trim();
|
|
165
|
+
m.domain = m.domain?.trim();
|
|
166
|
+
m.description = m.description?.trim();
|
|
167
|
+
if (m.commandDetails.length > 0) {
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
m.commandDetails = m.commandDetails
|
|
170
|
+
.map((c) => ({ ...c, name: c.name.trim(), description: c.description?.trim() }))
|
|
171
|
+
.filter((c) => {
|
|
172
|
+
if (!c.name || seen.has(c.name))
|
|
173
|
+
return false;
|
|
174
|
+
seen.add(c.name);
|
|
175
|
+
return true;
|
|
176
|
+
})
|
|
177
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
178
|
+
m.commands = m.commandDetails.map((c) => c.name);
|
|
179
|
+
}
|
|
180
|
+
return m;
|
|
181
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ClipProcessStatus } from './process.js';
|
|
2
|
+
import type { ClipConfig, ManifestCache } from './manifest.js';
|
|
3
|
+
export interface ClipProcessManagerOptions {
|
|
4
|
+
bunPath?: string;
|
|
5
|
+
clipsDir: string;
|
|
6
|
+
dataDir: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class ClipProcessManager {
|
|
9
|
+
private bunPath;
|
|
10
|
+
private clipsDir;
|
|
11
|
+
private dataDir;
|
|
12
|
+
private processes;
|
|
13
|
+
private startingUp;
|
|
14
|
+
private statuses;
|
|
15
|
+
private clipConfigs;
|
|
16
|
+
private shuttingDown;
|
|
17
|
+
private statusListeners;
|
|
18
|
+
private manifestListeners;
|
|
19
|
+
constructor(opts: ClipProcessManagerOptions);
|
|
20
|
+
/** Backward-compat: set a single status listener (wraps addStatusListener). */
|
|
21
|
+
set onStatusChange(fn: ((name: string, status: ClipProcessStatus, message: string) => void) | undefined);
|
|
22
|
+
/** Subscribe to clip status changes. Returns an unsubscribe function. */
|
|
23
|
+
addStatusListener(fn: (name: string, status: ClipProcessStatus, message: string) => void): () => void;
|
|
24
|
+
addManifestListener(fn: (clip: ClipConfig) => void): () => void;
|
|
25
|
+
private notifyManifestUpdate;
|
|
26
|
+
private resolveBun;
|
|
27
|
+
registerClip(config: ClipConfig): void;
|
|
28
|
+
unregisterClip(name: string): Promise<void>;
|
|
29
|
+
startClip(name: string): Promise<void>;
|
|
30
|
+
stopClip(name: string): Promise<void>;
|
|
31
|
+
stopAll(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Invoke a command on a clip. Auto-starts if sleeping, retries once on process crash.
|
|
34
|
+
* Application-level errors (ClipCommandError) are NOT retried.
|
|
35
|
+
* Corresponds to Pinix ProcessManager.Invoke().
|
|
36
|
+
*/
|
|
37
|
+
invoke(name: string, command: string, input?: unknown): Promise<unknown>;
|
|
38
|
+
invokeStream(name: string, command: string, input?: unknown, onChunk?: (chunk: unknown) => void): Promise<unknown>;
|
|
39
|
+
loadManifest(name: string): Promise<ManifestCache>;
|
|
40
|
+
clipStatus(name: string): {
|
|
41
|
+
status: ClipProcessStatus;
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
isRunning(name: string): boolean;
|
|
45
|
+
getRegisteredClips(): ClipConfig[];
|
|
46
|
+
/**
|
|
47
|
+
* Load clip configs from the clips directory.
|
|
48
|
+
* Reads clip-config.json if present, or scans subdirectories for clip.json files.
|
|
49
|
+
*/
|
|
50
|
+
loadInstalledClips(): Promise<void>;
|
|
51
|
+
private ensureProcess;
|
|
52
|
+
private spawnProcess;
|
|
53
|
+
private ensureClipDataDir;
|
|
54
|
+
private removeIfSame;
|
|
55
|
+
private setStatus;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=process-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQ/D,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,UAAU,CAA2C;IAC7D,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,eAAe,CAEhB;IACP,OAAO,CAAC,iBAAiB,CAAyC;gBAEtD,IAAI,EAAE,yBAAyB;IAM3C,+EAA+E;IAC/E,IAAI,cAAc,CAAC,EAAE,EACjB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GACpE,SAAS,EAQZ;IAED,yEAAyE;IACzE,iBAAiB,CACf,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACrE,MAAM,IAAI;IAQb,mBAAmB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAQ/D,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,UAAU;IAOlB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAOhC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxE,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,OAAO,CAAC;IAgBb,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAMxE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAKhC,kBAAkB,IAAI,UAAU,EAAE;IAIlC;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;YA0D3B,aAAa;YAwBb,YAAY;IA0C1B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,SAAS;CAUlB"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { ClipProcess, ClipCommandError, } from './process.js';
|
|
5
|
+
export class ClipProcessManager {
|
|
6
|
+
bunPath;
|
|
7
|
+
clipsDir;
|
|
8
|
+
dataDir;
|
|
9
|
+
processes = new Map();
|
|
10
|
+
startingUp = new Map();
|
|
11
|
+
statuses = new Map();
|
|
12
|
+
clipConfigs = new Map();
|
|
13
|
+
shuttingDown = new Set();
|
|
14
|
+
statusListeners = [];
|
|
15
|
+
manifestListeners = [];
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.bunPath = opts.bunPath ?? '';
|
|
18
|
+
this.clipsDir = opts.clipsDir;
|
|
19
|
+
this.dataDir = opts.dataDir;
|
|
20
|
+
}
|
|
21
|
+
/** Backward-compat: set a single status listener (wraps addStatusListener). */
|
|
22
|
+
set onStatusChange(fn) {
|
|
23
|
+
// Remove any previously installed compat listener, then add the new one.
|
|
24
|
+
// We store the unsubscribe handle on the function itself to avoid an extra field.
|
|
25
|
+
const prev = this._compatUnsub;
|
|
26
|
+
if (prev)
|
|
27
|
+
prev();
|
|
28
|
+
if (fn) {
|
|
29
|
+
this._compatUnsub = this.addStatusListener(fn);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Subscribe to clip status changes. Returns an unsubscribe function. */
|
|
33
|
+
addStatusListener(fn) {
|
|
34
|
+
this.statusListeners.push(fn);
|
|
35
|
+
return () => {
|
|
36
|
+
const idx = this.statusListeners.indexOf(fn);
|
|
37
|
+
if (idx >= 0)
|
|
38
|
+
this.statusListeners.splice(idx, 1);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
addManifestListener(fn) {
|
|
42
|
+
this.manifestListeners.push(fn);
|
|
43
|
+
return () => {
|
|
44
|
+
const idx = this.manifestListeners.indexOf(fn);
|
|
45
|
+
if (idx >= 0)
|
|
46
|
+
this.manifestListeners.splice(idx, 1);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
notifyManifestUpdate(clip) {
|
|
50
|
+
for (const fn of this.manifestListeners) {
|
|
51
|
+
try {
|
|
52
|
+
fn(clip);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* listener error should not break spawn */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
resolveBun() {
|
|
60
|
+
if (!this.bunPath) {
|
|
61
|
+
this.bunPath = findBunBinary();
|
|
62
|
+
}
|
|
63
|
+
return this.bunPath;
|
|
64
|
+
}
|
|
65
|
+
registerClip(config) {
|
|
66
|
+
this.clipConfigs.set(config.name, config);
|
|
67
|
+
if (!this.statuses.has(config.name)) {
|
|
68
|
+
this.setStatus(config.name, 'sleeping', 'registered');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async unregisterClip(name) {
|
|
72
|
+
this.shuttingDown.add(name);
|
|
73
|
+
try {
|
|
74
|
+
const startup = this.startingUp.get(name);
|
|
75
|
+
if (startup) {
|
|
76
|
+
this.startingUp.delete(name);
|
|
77
|
+
await startup.then((proc) => proc.stop()).catch(() => { });
|
|
78
|
+
}
|
|
79
|
+
if (this.processes.has(name)) {
|
|
80
|
+
await this.stopClip(name);
|
|
81
|
+
}
|
|
82
|
+
this.clipConfigs.delete(name);
|
|
83
|
+
this.statuses.delete(name);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.shuttingDown.delete(name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async startClip(name) {
|
|
90
|
+
await this.ensureProcess(name);
|
|
91
|
+
}
|
|
92
|
+
async stopClip(name) {
|
|
93
|
+
this.shuttingDown.add(name);
|
|
94
|
+
try {
|
|
95
|
+
const proc = this.processes.get(name);
|
|
96
|
+
if (!proc)
|
|
97
|
+
return;
|
|
98
|
+
await proc.stop();
|
|
99
|
+
this.processes.delete(name);
|
|
100
|
+
this.setStatus(name, 'sleeping', 'stopped');
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.shuttingDown.delete(name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async stopAll() {
|
|
107
|
+
// Mark all clips as shutting down before any async work
|
|
108
|
+
for (const name of this.processes.keys())
|
|
109
|
+
this.shuttingDown.add(name);
|
|
110
|
+
for (const name of this.startingUp.keys())
|
|
111
|
+
this.shuttingDown.add(name);
|
|
112
|
+
const stops = Array.from(this.processes.keys()).map((name) => this.stopClip(name));
|
|
113
|
+
// Also abort any in-flight startups (register window)
|
|
114
|
+
for (const [, startup] of this.startingUp) {
|
|
115
|
+
stops.push(startup.then((proc) => proc.stop()).catch(() => { }));
|
|
116
|
+
}
|
|
117
|
+
this.startingUp.clear();
|
|
118
|
+
await Promise.allSettled(stops);
|
|
119
|
+
// shuttingDown is cleared by individual stopClip finally blocks;
|
|
120
|
+
// clear any remaining entries from startingUp-only clips.
|
|
121
|
+
this.shuttingDown.clear();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Invoke a command on a clip. Auto-starts if sleeping, retries once on process crash.
|
|
125
|
+
* Application-level errors (ClipCommandError) are NOT retried.
|
|
126
|
+
* Corresponds to Pinix ProcessManager.Invoke().
|
|
127
|
+
*/
|
|
128
|
+
async invoke(name, command, input) {
|
|
129
|
+
try {
|
|
130
|
+
const proc = await this.ensureProcess(name);
|
|
131
|
+
const result = await proc.invoke(command, input);
|
|
132
|
+
return result.output;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (err instanceof ClipCommandError)
|
|
136
|
+
throw err;
|
|
137
|
+
const old = this.processes.get(name);
|
|
138
|
+
if (old?.alive())
|
|
139
|
+
await old.stop().catch(() => { });
|
|
140
|
+
this.processes.delete(name);
|
|
141
|
+
const proc = await this.ensureProcess(name);
|
|
142
|
+
const result = await proc.invoke(command, input);
|
|
143
|
+
return result.output;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async invokeStream(name, command, input, onChunk) {
|
|
147
|
+
try {
|
|
148
|
+
const proc = await this.ensureProcess(name);
|
|
149
|
+
const result = await proc.invokeStream(command, input, onChunk);
|
|
150
|
+
return result.output;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof ClipCommandError)
|
|
154
|
+
throw err;
|
|
155
|
+
const old = this.processes.get(name);
|
|
156
|
+
if (old?.alive())
|
|
157
|
+
await old.stop().catch(() => { });
|
|
158
|
+
this.processes.delete(name);
|
|
159
|
+
const proc = await this.ensureProcess(name);
|
|
160
|
+
const result = await proc.invokeStream(command, input, onChunk);
|
|
161
|
+
return result.output;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async loadManifest(name) {
|
|
165
|
+
const proc = await this.ensureProcess(name);
|
|
166
|
+
const manifest = proc.getManifest();
|
|
167
|
+
if (!manifest)
|
|
168
|
+
throw new Error(`clip "${name}" has no manifest`);
|
|
169
|
+
return manifest;
|
|
170
|
+
}
|
|
171
|
+
clipStatus(name) {
|
|
172
|
+
const entry = this.statuses.get(name);
|
|
173
|
+
if (!entry)
|
|
174
|
+
return { status: 'sleeping', message: 'unknown' };
|
|
175
|
+
return { status: entry.status, message: entry.message };
|
|
176
|
+
}
|
|
177
|
+
isRunning(name) {
|
|
178
|
+
const proc = this.processes.get(name);
|
|
179
|
+
return proc !== undefined && proc.alive();
|
|
180
|
+
}
|
|
181
|
+
getRegisteredClips() {
|
|
182
|
+
return Array.from(this.clipConfigs.values());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Load clip configs from the clips directory.
|
|
186
|
+
* Reads clip-config.json if present, or scans subdirectories for clip.json files.
|
|
187
|
+
*/
|
|
188
|
+
async loadInstalledClips() {
|
|
189
|
+
const configFile = path.join(path.dirname(this.clipsDir), 'clip-config.json');
|
|
190
|
+
if (fs.existsSync(configFile)) {
|
|
191
|
+
try {
|
|
192
|
+
const content = fs.readFileSync(configFile, 'utf-8');
|
|
193
|
+
const configs = JSON.parse(content);
|
|
194
|
+
for (const config of configs) {
|
|
195
|
+
this.registerClip(config);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
console.warn('[clip-manager] failed to parse clip-config.json, scanning directory');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!fs.existsSync(this.clipsDir))
|
|
204
|
+
return;
|
|
205
|
+
const entries = fs.readdirSync(this.clipsDir, { withFileTypes: true });
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
if (!entry.isDirectory())
|
|
208
|
+
continue;
|
|
209
|
+
const clipDir = path.join(this.clipsDir, entry.name);
|
|
210
|
+
// Try clip.json first, fall back to package.json
|
|
211
|
+
const clipJsonPath = path.join(clipDir, 'clip.json');
|
|
212
|
+
const pkgJsonPath = path.join(clipDir, 'package.json');
|
|
213
|
+
if (fs.existsSync(clipJsonPath)) {
|
|
214
|
+
try {
|
|
215
|
+
const clipJson = JSON.parse(fs.readFileSync(clipJsonPath, 'utf-8'));
|
|
216
|
+
this.registerClip({
|
|
217
|
+
name: clipJson.name || entry.name,
|
|
218
|
+
package: clipJson.name,
|
|
219
|
+
version: clipJson.version,
|
|
220
|
+
source: clipDir,
|
|
221
|
+
path: clipDir,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
console.warn(`[clip-manager] failed to parse clip.json in ${clipDir}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (fs.existsSync(pkgJsonPath)) {
|
|
229
|
+
try {
|
|
230
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
231
|
+
this.registerClip({
|
|
232
|
+
name: pkgJson.name || entry.name,
|
|
233
|
+
package: pkgJson.name,
|
|
234
|
+
version: pkgJson.version,
|
|
235
|
+
source: clipDir,
|
|
236
|
+
path: clipDir,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
console.warn(`[clip-manager] failed to parse package.json in ${clipDir}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// --- internal ---
|
|
246
|
+
async ensureProcess(name) {
|
|
247
|
+
if (this.shuttingDown.has(name)) {
|
|
248
|
+
throw new Error(`clip "${name}" is shutting down`);
|
|
249
|
+
}
|
|
250
|
+
const existing = this.processes.get(name);
|
|
251
|
+
if (existing && existing.alive())
|
|
252
|
+
return existing;
|
|
253
|
+
// Deduplicate concurrent cold-start requests
|
|
254
|
+
const inflight = this.startingUp.get(name);
|
|
255
|
+
if (inflight)
|
|
256
|
+
return inflight;
|
|
257
|
+
const config = this.clipConfigs.get(name);
|
|
258
|
+
if (!config)
|
|
259
|
+
throw new Error(`clip "${name}" not registered`);
|
|
260
|
+
const startup = this.spawnProcess(config);
|
|
261
|
+
this.startingUp.set(name, startup);
|
|
262
|
+
try {
|
|
263
|
+
return await startup;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
this.startingUp.delete(name);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async spawnProcess(config) {
|
|
270
|
+
const proc = new ClipProcess(config);
|
|
271
|
+
const clipDataDir = this.ensureClipDataDir(config);
|
|
272
|
+
proc.spawn(this.resolveBun(), {
|
|
273
|
+
PINIX_DATA_DIR: clipDataDir,
|
|
274
|
+
});
|
|
275
|
+
this.setStatus(config.name, 'running', 'starting');
|
|
276
|
+
try {
|
|
277
|
+
await proc.waitReady();
|
|
278
|
+
// Only expose to concurrent callers after registration completes
|
|
279
|
+
this.processes.set(config.name, proc);
|
|
280
|
+
this.setStatus(config.name, 'running', 'ready');
|
|
281
|
+
// Always cache manifest so Provider registrations have commands
|
|
282
|
+
const manifest = proc.getManifest();
|
|
283
|
+
if (manifest) {
|
|
284
|
+
config.manifest = manifest;
|
|
285
|
+
this.notifyManifestUpdate(config);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
290
|
+
this.setStatus(config.name, 'error', message);
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
// Monitor process exit in background
|
|
294
|
+
proc.waitDone().then(() => {
|
|
295
|
+
this.removeIfSame(config.name, proc);
|
|
296
|
+
const exitErr = proc.getError();
|
|
297
|
+
if (exitErr) {
|
|
298
|
+
this.setStatus(config.name, 'error', exitErr.message);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
this.setStatus(config.name, 'sleeping', 'exited');
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return proc;
|
|
305
|
+
}
|
|
306
|
+
ensureClipDataDir(config) {
|
|
307
|
+
const dir = path.join(this.dataDir, config.name);
|
|
308
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
309
|
+
return dir;
|
|
310
|
+
}
|
|
311
|
+
removeIfSame(name, proc) {
|
|
312
|
+
if (this.processes.get(name) === proc) {
|
|
313
|
+
this.processes.delete(name);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
setStatus(name, status, message) {
|
|
317
|
+
this.statuses.set(name, { status, message, since: new Date() });
|
|
318
|
+
for (const fn of this.statusListeners) {
|
|
319
|
+
try {
|
|
320
|
+
fn(name, status, message);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
/* listener must not break status propagation */
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function findBunBinary() {
|
|
329
|
+
const isWindows = process.platform === 'win32';
|
|
330
|
+
if (!isWindows) {
|
|
331
|
+
const candidates = [
|
|
332
|
+
path.join(process.env.HOME || '', '.bun', 'bin', 'bun'),
|
|
333
|
+
'/usr/local/bin/bun',
|
|
334
|
+
'/opt/homebrew/bin/bun',
|
|
335
|
+
];
|
|
336
|
+
for (const candidate of candidates) {
|
|
337
|
+
if (fs.existsSync(candidate))
|
|
338
|
+
return candidate;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// PATH lookup — platform-aware
|
|
342
|
+
const lookupCmd = isWindows ? 'where.exe' : 'which';
|
|
343
|
+
const lookupArg = isWindows ? 'bun.exe' : 'bun';
|
|
344
|
+
try {
|
|
345
|
+
const result = execFileSync(lookupCmd, [lookupArg], { encoding: 'utf-8' }).trim();
|
|
346
|
+
const firstLine = result.split('\n')[0]?.trim();
|
|
347
|
+
if (firstLine)
|
|
348
|
+
return firstLine;
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// not found in PATH
|
|
352
|
+
}
|
|
353
|
+
throw new Error('bun binary not found — install Bun (https://bun.sh) or set bunPath option');
|
|
354
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ClipConfig, type ManifestCache } from './manifest.js';
|
|
2
|
+
export type ClipProcessStatus = 'sleeping' | 'running' | 'error';
|
|
3
|
+
export interface InvokeEvent {
|
|
4
|
+
type: string;
|
|
5
|
+
output?: unknown;
|
|
6
|
+
error?: {
|
|
7
|
+
message: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
};
|
|
10
|
+
processExit?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface InvokeResult {
|
|
13
|
+
output: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare class ClipCommandError extends Error {
|
|
16
|
+
readonly code?: string;
|
|
17
|
+
constructor(message: string, code?: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class ClipProcess {
|
|
20
|
+
readonly clip: ClipConfig;
|
|
21
|
+
private child;
|
|
22
|
+
private reader;
|
|
23
|
+
private writer;
|
|
24
|
+
private registered;
|
|
25
|
+
private manifest;
|
|
26
|
+
private pending;
|
|
27
|
+
private nextId;
|
|
28
|
+
private aborted;
|
|
29
|
+
private stopping;
|
|
30
|
+
private readyDeferred;
|
|
31
|
+
private readyResolved;
|
|
32
|
+
private doneDeferred;
|
|
33
|
+
private doneResolved;
|
|
34
|
+
private exitError;
|
|
35
|
+
constructor(clip: ClipConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Spawn the Bun subprocess and start the IPC read loop.
|
|
38
|
+
* Corresponds to Pinix startLocked().
|
|
39
|
+
*/
|
|
40
|
+
spawn(bunPath: string, env: Record<string, string>): void;
|
|
41
|
+
waitReady(timeoutMs?: number): Promise<void>;
|
|
42
|
+
waitDone(): Promise<void>;
|
|
43
|
+
invoke(command: string, input?: unknown): Promise<InvokeResult>;
|
|
44
|
+
invokeStream(command: string, input?: unknown, onChunk?: (chunk: unknown) => void): Promise<InvokeResult>;
|
|
45
|
+
stop(timeoutMs?: number): Promise<void>;
|
|
46
|
+
alive(): boolean;
|
|
47
|
+
getManifest(): ManifestCache | null;
|
|
48
|
+
getError(): Error | undefined;
|
|
49
|
+
private readLoop;
|
|
50
|
+
private handleMessage;
|
|
51
|
+
private handleRegister;
|
|
52
|
+
private dispatchInvokeEvent;
|
|
53
|
+
private handleData;
|
|
54
|
+
private send;
|
|
55
|
+
private signalReady;
|
|
56
|
+
private finish;
|
|
57
|
+
private abort;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAInB,MAAM,eAAe,CAAC;AAevB,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBACX,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAK3C;AAgBD,qBAAa,WAAW;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAE1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA6B;IAE3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,OAAO,CAAmD;IAClE,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAoB;gBAEzB,IAAI,EAAE,UAAU;IAI5B;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAwCnD,SAAS,CAAC,SAAS,GAAE,MAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAInB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAI/D,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,YAAY,CAAC;IA2DlB,IAAI,CAAC,SAAS,GAAE,MAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBnE,KAAK,IAAI,OAAO;IAShB,WAAW,IAAI,aAAa,GAAG,IAAI;IAInC,QAAQ,IAAI,KAAK,GAAG,SAAS;YAMf,QAAQ;IAWtB,OAAO,CAAC,aAAa;IA6CrB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,UAAU;YASJ,IAAI;IAKlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,MAAM;IAiCd,OAAO,CAAC,KAAK;CAOd"}
|