@promptframe/cli 0.1.63 → 0.1.66

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/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # @promptframe/cli
2
2
 
3
+ `package` 和目录形式的 `upload` 使用所选组件目录及其子目录中的 `.gitignore`,支持嵌套覆盖、`!` 和目录规则;不读取组件目录外的 Git 配置。`node_modules`、`dist`、`.git`、`.promptframe`、`.component-packages` 仍固定排除。将本地出图和测试产物放在明确忽略的目录中。
4
+
5
+ 必需文件、manifest 声明的源码/props schema 和 `public/` 资源不能被静默排除;规则命中时返回 `package.required_file.ignored`,应修正规则后再打包。保留文件仍接受原安全扫描,原始 lockfile 不入包,脱敏 lockfile evidence 保留。
6
+
3
7
  PromptFrame component authoring CLI.
4
8
 
5
9
  Use it to inspect the public component standard, check component folders, validate manifests, inspect the local preview envelope, package source archives, upload components, check build status, rebuild evidence indexes, and rerun layout/security probes.
6
10
 
7
- Current source baseline is `@promptframe/cli@0.1.63`, `@promptframe/contracts@0.1.31`, `@promptframe/component-kit@0.1.25`, and `create-promptframe-component@0.1.53`. The published CLI consumes the contracts AST-aware public security policy evaluator, reports `securityPolicyDigest` / `securityEvaluatorMode` in JSON output, rejects red-team browser capability vectors such as Image/Audio beacons, Worker/SharedWorker, `window.open`, `postMessage`, dynamic script/iframe creation, browser fingerprinting, storage/cookie access, and dangerous HTML, reports `publicResources` for component-level public assets, keeps native `<img>` / `<video>` source checks case-sensitive so Remotion `<Img>` / `<Video>` remain valid, validates preview props against statically declared `propsSchema` fields and requires readable public prop descriptions, enforces strict Slot-first layout/source diagnostics, supports endpoint discovery, project list/current diagnostics, self-service CI token create/list/revoke, single-component and workspace GitHub workflow setup with `promptframe-workflow-version: 2`, `setup-ci --upgrade` stale-template repair, dynamic workspace matrix discovery, `RUNNER_LABELS` and pnpm 10 workflow support, pnpm workspace root lockfile evidence without component symlinks, `promptframe-workspace.json` validation, workspace root `check .` / `upload .` auto-detection, explicit `workspace:*` shared-package diagnostics, workspace upload source metadata headers, packages sanitized lockfile evidence for platform admission, release notes forwarding to upload, stderr NDJSON upload progress for `--json`, human-readable token kind/expiry summaries for `login`, `whoami`, and authenticated `upload`, server version auto-bump notes, and supports `status --fail-on-build-failed` for CI build-status gating.
11
+ Current source baseline is `@promptframe/cli@0.1.66`, `@promptframe/contracts@0.1.34`, `@promptframe/component-kit@0.1.28`, and `create-promptframe-component@0.1.56`. The published CLI consumes the contracts AST-aware public security policy evaluator, reports `securityPolicyDigest` / `securityEvaluatorMode` in JSON output, rejects red-team browser capability vectors such as Image/Audio beacons, Worker/SharedWorker, `window.open`, `postMessage`, dynamic script/iframe creation, browser fingerprinting, storage/cookie access, and dangerous HTML, reports `publicResources` for component-level public assets, keeps native `<img>` / `<video>` source checks case-sensitive so Remotion `<Img>` / `<Video>` remain valid, validates preview props against statically declared `propsSchema` fields and requires readable public prop descriptions, enforces strict Slot-first layout/source diagnostics, supports endpoint discovery, project list/current diagnostics, self-service CI token create/list/revoke, single-component and workspace GitHub workflow setup with `promptframe-workflow-version: 2`, `setup-ci --upgrade` stale-template repair, dynamic workspace matrix discovery, `RUNNER_LABELS` and pnpm 10 workflow support, pnpm workspace root lockfile evidence without component symlinks, `promptframe-workspace.json` validation, workspace root `check .` / `upload .` auto-detection, explicit `workspace:*` shared-package diagnostics, workspace upload source metadata headers, packages sanitized lockfile evidence for platform admission, release notes forwarding to upload, stderr NDJSON upload progress for `--json`, human-readable token kind/expiry summaries for `login`, `whoami`, and authenticated `upload`, server version auto-bump notes, and supports `status --fail-on-build-failed` for CI build-status gating.
8
12
 
9
13
  ```bash
10
14
  npm install -D @promptframe/cli
@@ -183,3 +187,22 @@ Components generated by `create-promptframe-component` include `.promptframe/sca
183
187
  - `freshness.status`: `current`, `warning`, `upload_blocking`, or `security_breaking`.
184
188
 
185
189
  `validate` and `package` consume public policies from `@promptframe/contracts`: required files, preview limits, deterministic frame-driven source rules, dependency policy, and security policy. Source files are evaluated with the contracts AST-aware security evaluator so alias usage such as `const BC = BroadcastChannel; new BC()` is rejected while ordinary comments or strings mentioning a blocked API do not fail validation. `runtime.deterministic.fps_hardcoded_timing` is a manual-review validation gate: hardcoded frame timing in Remotion contexts fails `validate` with a repair hint to use `secondsToFrames`, `createDurationTimeline`, `createRevealPhases`, `createFillProgress`, or `timeline.at`. JSON, Markdown, and package metadata still use bounded pattern fallback for install scripts, prompt-injection strings, and remote URL signals. These local checks are early author feedback; the platform admission pipeline remains the final trust gate.
190
+
191
+ ## 工作区开发连接
192
+
193
+ 带有 `promptframe-workspace.json` 的工程使用一个本机服务连接 Studio:
194
+
195
+ ```bash
196
+ promptframe dev . --studio-origin https://your-studio.example
197
+ promptframe dev . --studio-origin https://your-studio.example --idle-timeout 30
198
+ promptframe dev . --studio-origin https://your-studio.example --replace
199
+ promptframe dev stop .
200
+ ```
201
+
202
+ 将启动时输出的完整 JSON 粘贴到同一浏览器主机的 Studio 开发连接面板。服务运行期间连接没有固定十分钟截止;没有推送连接且没有请求持续30分钟后正常退出,`--idle-timeout 0` 可关闭闲置退出。推送心跳默认为25秒。普通单组件 Vite 预览仍使用原5173入口。
203
+
204
+ 工作区默认端口为24000—24999,冲突时在段内选择并记住可用端口;`--port` 优先。`.promptframe/dev/` 保存本机连接ID、端口及实例锁,并自行忽略Git,不含连接凭据。重启后ID保持、凭据重新生成:在Studio重新粘贴一次新JSON即可继续使用同ID节点。旧随机ID的节点需要手动重新绑定一次。本地连接v2需要相容的Studio;旧v1连接仍遵守原有效期。
205
+
206
+ 关闭开发面板不停止连接;切换账号或作品、关闭页面会断开。刷新页面后重新粘贴连接JSON。停止或替换仅操作本工作区的实际实例;无法确认实例退出时命令报错,不自动强制终止进程。
207
+
208
+ 上传组件后,用准确的正式 `componentRef` 替换作品节点引用并移除该节点的 `componentSource`。仅上传成功不会自动改变作品的选源。确认作品已使用正式引用且不再需要本地预览后,可执行 `promptframe dev stop .`。
@@ -0,0 +1,22 @@
1
+ /** Same CLI process, filesystem-local owner control. The Studio HTTP surface
2
+ * remains GET-only, and stop never signals a numeric PID. */
3
+ export declare function stopDevelopmentOwner(root: string, reason?: 'stop' | 'replace'): Promise<{
4
+ stopped: boolean;
5
+ stale?: undefined;
6
+ acknowledged?: undefined;
7
+ } | {
8
+ stopped: boolean;
9
+ stale: boolean;
10
+ acknowledged?: undefined;
11
+ } | {
12
+ stopped: boolean;
13
+ acknowledged: boolean;
14
+ stale?: undefined;
15
+ }>;
16
+ export declare function acquireDevelopmentOwner(root: string, onStop: (reason: 'stop' | 'replace' | 'fatal') => void): Promise<Readonly<{
17
+ connectionId: string;
18
+ preferredPort: number;
19
+ recordPort(port: number): void;
20
+ close(): Promise<void>;
21
+ }>>;
22
+ //# sourceMappingURL=development-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"development-owner.d.ts","sourceRoot":"","sources":["../src/development-owner.ts"],"names":[],"mappings":"AAmEA;6DAC6D;AAC7D,wBAAsB,oBAAoB,CAAC,IAAI,EAAC,MAAM,EAAC,MAAM,GAAC,MAAM,GAAC,SAAgB;;;;;;;;;;;;GAcpF;AACD,wBAAsB,uBAAuB,CAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAC,CAAC,MAAM,EAAC,MAAM,GAAC,SAAS,GAAC,OAAO,KAAG,IAAI;;;qBAgClF,MAAM;;IAY3B"}
@@ -0,0 +1,253 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { constants, chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { createConnection, createServer } from 'node:net';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ const hex = /^[a-f0-9]{32}$/u;
8
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
9
+ function birth(pid) {
10
+ try {
11
+ process.kill(pid, 0);
12
+ }
13
+ catch (error) {
14
+ if (error.code === 'ESRCH')
15
+ return null;
16
+ throw Error('development.owner.identity_unavailable');
17
+ }
18
+ try {
19
+ if (process.platform === 'linux') {
20
+ const stat = readFileSync(`/proc/${pid}/stat`, 'utf8'), fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
21
+ return readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim() + ':' + fields[19];
22
+ }
23
+ if (process.platform === 'win32')
24
+ return execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
25
+ return execFileSync('ps', ['-p', String(pid), '-o', 'lstart=', '-o', 'uid='], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
26
+ }
27
+ catch {
28
+ try {
29
+ process.kill(pid, 0);
30
+ }
31
+ catch (error) {
32
+ if (error.code === 'ESRCH')
33
+ return null;
34
+ }
35
+ throw Error('development.owner.identity_unavailable');
36
+ }
37
+ }
38
+ function paths(root, create = false) {
39
+ root = realpathSync(root);
40
+ const directory = join(root, '.promptframe', 'dev'), rootDigest = createHash('sha256').update(root).digest('hex');
41
+ for (const path of [join(root, '.promptframe'), directory]) {
42
+ if (!existsSync(path)) {
43
+ if (!create)
44
+ continue;
45
+ mkdirSync(path, { mode: 0o700 });
46
+ }
47
+ const info = lstatSync(path);
48
+ if (!info.isDirectory() || info.isSymbolicLink() || process.getuid && info.uid !== process.getuid())
49
+ throw Error('development.owner.directory_unsafe');
50
+ }
51
+ const ignored = join(directory, '.gitignore');
52
+ if (create && !existsSync(ignored))
53
+ writeFileSync(ignored, '*\n', { flag: 'wx', mode: 0o600 });
54
+ return { directory, rootDigest, state: join(directory, 'state.json'), lock: join(directory, 'owner.json') };
55
+ }
56
+ function read(path) {
57
+ const info = lstatSync(path);
58
+ if (!info.isFile() || info.isSymbolicLink() || process.getuid && info.uid !== process.getuid())
59
+ throw Error('development.owner.record_unsafe');
60
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
61
+ try {
62
+ const raw = readFileSync(fd, 'utf8');
63
+ if (Buffer.byteLength(raw) > 4096)
64
+ throw Error('development.owner.record_invalid');
65
+ return JSON.parse(raw);
66
+ }
67
+ finally {
68
+ closeSync(fd);
69
+ }
70
+ }
71
+ function write(path, value) {
72
+ const fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
73
+ try {
74
+ writeFileSync(fd, JSON.stringify(value) + '\n');
75
+ fsyncSync(fd);
76
+ }
77
+ finally {
78
+ closeSync(fd);
79
+ }
80
+ }
81
+ function endpoint(owner) {
82
+ const name = `promptframe-dev-${process.getuid?.() ?? 'user'}-${owner.rootDigest.slice(0, 24)}`;
83
+ // Linux abstract sockets and Windows named pipes are kernel-owned: stale
84
+ // metadata cannot require an unsafe unlink/recreate arbitration sequence.
85
+ if (process.platform === 'linux')
86
+ return '\0' + name;
87
+ return process.platform === 'win32' ? `\\\\.\\pipe\\${name}` : join(tmpdir(), name + '.sock');
88
+ }
89
+ function ownerAt(path, rootDigest) {
90
+ if (!existsSync(path))
91
+ return null;
92
+ const value = read(path);
93
+ if (Object.keys(value).some(key => !['version', 'rootDigest', 'instanceId', 'connectionId', 'pid', 'birth', 'port'].includes(key)) || value.version !== 1 || typeof value.instanceId !== 'string' || typeof value.connectionId !== 'string' || value.rootDigest !== rootDigest || !hex.test(value.instanceId) || !hex.test(value.connectionId)
94
+ || !Number.isSafeInteger(value.pid) || value.pid < 1 || typeof value.birth !== 'string' || !value.birth || value.birth.length > 256
95
+ || value.port !== undefined && (!Number.isInteger(value.port) || value.port < 1 || value.port > 65535))
96
+ throw Error('development.owner.record_invalid');
97
+ return value;
98
+ }
99
+ function same(a, b) { return a.instanceId === b.instanceId && a.pid === b.pid && a.birth === b.birth && a.rootDigest === b.rootDigest; }
100
+ function removeOwned(path, owner) { const current = ownerAt(path, owner.rootDigest); if (current && same(current, owner))
101
+ unlinkSync(path); }
102
+ async function command(owner, action) {
103
+ return new Promise((resolve, reject) => {
104
+ const socket = createConnection(endpoint(owner));
105
+ let raw = '';
106
+ const timer = setTimeout(() => socket.destroy(Error('development.owner.control_unconfirmed')), 3000);
107
+ socket.setEncoding('utf8');
108
+ socket.once('connect', () => socket.end(JSON.stringify({ action, instanceId: owner.instanceId }) + '\n'));
109
+ socket.on('data', chunk => { raw += chunk; if (raw.length > 4096)
110
+ socket.destroy(Error('development.owner.control_invalid')); });
111
+ socket.once('error', reject);
112
+ socket.once('close', () => { clearTimeout(timer); try {
113
+ const result = JSON.parse(raw);
114
+ if (!same(result, owner) || result.connectionId !== owner.connectionId)
115
+ throw Error();
116
+ resolve(result);
117
+ }
118
+ catch {
119
+ reject(Error('development.owner.control_unconfirmed'));
120
+ } });
121
+ });
122
+ }
123
+ /** Same CLI process, filesystem-local owner control. The Studio HTTP surface
124
+ * remains GET-only, and stop never signals a numeric PID. */
125
+ export async function stopDevelopmentOwner(root, reason = 'stop') {
126
+ const p = paths(root), owner = ownerAt(p.lock, p.rootDigest);
127
+ if (!owner)
128
+ return { stopped: false };
129
+ const current = birth(owner.pid);
130
+ if (current !== owner.birth)
131
+ return { stopped: false, stale: true };
132
+ await command(owner, 'inspect');
133
+ let acknowledged = true;
134
+ try {
135
+ await command(owner, reason);
136
+ }
137
+ catch {
138
+ acknowledged = false;
139
+ }
140
+ // A lost control reply is observed under the same identity, never resent.
141
+ const until = Date.now() + 30_000;
142
+ while (Date.now() < until) {
143
+ const live = ownerAt(p.lock, p.rootDigest);
144
+ if (live && !same(live, owner))
145
+ throw Error('development.owner.changed');
146
+ if (!live && birth(owner.pid) !== owner.birth)
147
+ return { stopped: true, acknowledged };
148
+ await wait(50);
149
+ }
150
+ throw Error('development.owner.close_unconfirmed');
151
+ }
152
+ export async function acquireDevelopmentOwner(root, onStop) {
153
+ const p = paths(root, true);
154
+ let state;
155
+ if (!existsSync(p.state)) {
156
+ try {
157
+ write(p.state, { version: 1, connectionId: randomBytes(16).toString('hex') });
158
+ }
159
+ catch (error) {
160
+ if (error.code !== 'EEXIST')
161
+ throw error;
162
+ }
163
+ }
164
+ state = read(p.state);
165
+ if (Object.keys(state).some(key => !['version', 'connectionId', 'port'].includes(key)) || state.version !== 1 || typeof state.connectionId !== 'string' || !hex.test(state.connectionId) || state.port !== undefined && (!Number.isInteger(state.port) || state.port < 1 || state.port > 65535))
166
+ throw Error('development.owner.state_invalid');
167
+ let owner = { version: 1, rootDigest: p.rootDigest, instanceId: randomBytes(16).toString('hex'), connectionId: state.connectionId, pid: process.pid, birth: birth(process.pid) };
168
+ let ownsRecord = false;
169
+ let closing = false;
170
+ const sockets = new Set();
171
+ let control;
172
+ try {
173
+ control = createServer({ allowHalfOpen: true }, socket => {
174
+ sockets.add(socket);
175
+ socket.once('close', () => sockets.delete(socket));
176
+ socket.setTimeout(3000, () => socket.destroy());
177
+ socket.setEncoding('utf8');
178
+ let raw = '';
179
+ socket.on('data', chunk => {
180
+ raw += chunk;
181
+ if (raw.length > 1024) {
182
+ socket.destroy();
183
+ return;
184
+ }
185
+ if (!raw.endsWith('\n'))
186
+ return;
187
+ try {
188
+ const input = JSON.parse(raw);
189
+ if (input.instanceId !== owner.instanceId || !['inspect', 'stop', 'replace'].includes(input.action))
190
+ throw Error();
191
+ socket.end(JSON.stringify(owner));
192
+ if (input.action !== 'inspect' && !closing) {
193
+ closing = true;
194
+ onStop(input.action);
195
+ }
196
+ }
197
+ catch {
198
+ socket.destroy();
199
+ }
200
+ });
201
+ });
202
+ await new Promise((resolve, reject) => { control.once('error', reject); control.listen(endpoint(owner), () => { control.removeListener('error', reject); resolve(); }); });
203
+ control.on('error', () => { if (!closing) {
204
+ closing = true;
205
+ onStop('fatal');
206
+ } });
207
+ if (process.platform !== 'win32' && process.platform !== 'linux')
208
+ chmodSync(endpoint(owner), 0o600);
209
+ // All stale reclamation, new record creation and state updates happen
210
+ // while this process owns the fixed kernel endpoint.
211
+ const previous = ownerAt(p.lock, p.rootDigest);
212
+ if (previous) {
213
+ if (birth(previous.pid) === previous.birth)
214
+ throw Error('development.owner.previous_exit_unconfirmed');
215
+ removeOwned(p.lock, previous);
216
+ }
217
+ write(p.lock, owner);
218
+ ownsRecord = true;
219
+ let closePromise;
220
+ return Object.freeze({ connectionId: state.connectionId,
221
+ preferredPort: state.port && state.port >= 24000 && state.port <= 24999 ? state.port : 24000 + parseInt(createHash('sha256').update(state.connectionId).digest('hex').slice(0, 8), 16) % 1000,
222
+ recordPort(port) {
223
+ const current = ownerAt(p.lock, p.rootDigest);
224
+ if (!current || !same(current, owner))
225
+ throw Error('development.owner.changed');
226
+ state = { ...state, port };
227
+ owner = { ...owner, port };
228
+ for (const [path, value] of [[p.state, state], [p.lock, owner]]) {
229
+ const temporary = path + '.' + owner.instanceId;
230
+ write(temporary, value);
231
+ renameSync(temporary, path);
232
+ }
233
+ },
234
+ close() {
235
+ return closePromise ??= (async () => {
236
+ removeOwned(p.lock, owner);
237
+ for (const socket of sockets)
238
+ socket.destroy();
239
+ await new Promise((resolve, reject) => control.close(error => error ? reject(error) : resolve()));
240
+ })();
241
+ },
242
+ });
243
+ }
244
+ catch (error) {
245
+ if (ownsRecord)
246
+ removeOwned(p.lock, owner);
247
+ control?.close();
248
+ if (error.code === 'EADDRINUSE')
249
+ throw Error('development.owner.already_running');
250
+ throw error;
251
+ }
252
+ }
253
+ //# sourceMappingURL=development-owner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"development-owner.js","sourceRoot":"","sources":["../src/development-owner.ts"],"names":[],"mappings":"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"}
@@ -14,12 +14,24 @@ export declare function startDevelopmentWorkspace(options: {
14
14
  studioOrigin: string;
15
15
  host?: '127.0.0.1' | '::1';
16
16
  port?: number;
17
+ connectionId?: string;
18
+ portFallback?: boolean;
19
+ idleTimeoutMs?: number;
20
+ heartbeatMs?: number;
17
21
  }): Promise<Readonly<{
18
22
  connectionId: string;
19
23
  url: `http://${string}`;
20
24
  studioOrigin: string;
21
25
  credential: string;
22
- expiresAt: string;
23
- close(): Promise<void>;
26
+ lifecycle: {
27
+ mode: "until_stopped";
28
+ idleTimeoutSeconds: number;
29
+ heartbeatSeconds: number;
30
+ };
31
+ closed: Promise<{
32
+ reason: string;
33
+ failed: boolean;
34
+ }>;
35
+ close: (reason?: string) => Promise<void>;
24
36
  }>>;
25
37
  //# sourceMappingURL=development-workspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"development-workspace.d.ts","sourceRoot":"","sources":["../src/development-workspace.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKjE,MAAM,WAAW,0BAA0B;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAExE;gFACgF;AAChF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,GAAG,mBAAmB,CAuD9G;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,OAAO,EAAE;IACvD,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IACnF,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CAC3C;;;;;;;IAsMA"}
1
+ {"version":3,"file":"development-workspace.d.ts","sourceRoot":"","sources":["../src/development-workspace.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKjE,MAAM,WAAW,0BAA0B;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAExE;gFACgF;AAChF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,GAAG,mBAAmB,CAuD9G;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,OAAO,EAAE;IACvD,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IACnF,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAC,MAAM,CAAC;IAAC,YAAY,CAAC,EAAC,OAAO,CAAC;IACvF,aAAa,CAAC,EAAC,MAAM,CAAC;IAAC,WAAW,CAAC,EAAC,MAAM,CAAC;CAC5C;;;;;;;;;;;gBA+LwC,MAAM;gBAAQ,OAAO;;;IAuB7D"}
@@ -102,8 +102,12 @@ export async function startDevelopmentWorkspace(options) {
102
102
  if (modules.size !== options.modules.length || new Set([...modules.values()].map(module => module.path)).size !== modules.size) {
103
103
  throw new Error('development.registry.duplicate_module');
104
104
  }
105
- const credential = randomBytes(32).toString('hex'), connectionId = randomBytes(16).toString('hex');
106
- const expiresAt = Date.now() + 10 * 60_000;
105
+ const credential = randomBytes(32).toString('hex'), connectionId = options.connectionId ?? randomBytes(16).toString('hex');
106
+ const idleTimeoutMs = options.idleTimeoutMs ?? 30 * 60_000, heartbeatMs = options.heartbeatMs ?? 25_000;
107
+ if (!/^[a-f0-9]{32}$/u.test(connectionId) || !Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 0 || !Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1)
108
+ throw Error('development.connection.invalid_lifecycle');
109
+ const lifecycle = { mode: 'until_stopped', idleTimeoutSeconds: idleTimeoutMs / 1000, heartbeatSeconds: heartbeatMs / 1000 };
110
+ let lastActivity = performance.now(), activeRequests = 0;
107
111
  const controller = new AbortController(), listeners = new Set();
108
112
  const pending = new Set(), resident = new Map();
109
113
  let pumping, checkingId;
@@ -124,8 +128,8 @@ export async function startDevelopmentWorkspace(options) {
124
128
  return built;
125
129
  },
126
130
  });
127
- const registry = () => ({ contractVersion: 'promptframe-development.v1', connectionId,
128
- expiresAt: new Date(expiresAt).toISOString(), watcherDiagnostic, modules: [...modules.keys()].map(id => {
131
+ const registry = () => ({ contractVersion: 'promptframe-development.v2', connectionId,
132
+ lifecycle, watcherDiagnostic, modules: [...modules.keys()].map(id => {
129
133
  const state = coordinator.state(id), receipt = coordinator.buildReceipt(id);
130
134
  const checking = pending.has(id) || checkingId === id;
131
135
  return { moduleId: id, status: checking ? 'checking' : state?.status ?? 'checking',
@@ -148,7 +152,7 @@ export async function startDevelopmentWorkspace(options) {
148
152
  }
149
153
  };
150
154
  const enqueue = (id) => {
151
- if (closed || Date.now() >= expiresAt || !modules.has(id))
155
+ if (closed || !modules.has(id))
152
156
  return;
153
157
  pending.add(id);
154
158
  if (pumping)
@@ -166,7 +170,7 @@ export async function startDevelopmentWorkspace(options) {
166
170
  checkingId = undefined;
167
171
  notify();
168
172
  }
169
- }).finally(() => { pumping = undefined; });
173
+ }).catch(() => { void close('fatal').catch(() => undefined); }).finally(() => { pumping = undefined; });
170
174
  };
171
175
  // Explicit reads recapture too. Watch delivery/debounce is never authority.
172
176
  const observe = (onlyId) => {
@@ -214,8 +218,6 @@ export async function startDevelopmentWorkspace(options) {
214
218
  }
215
219
  if (request.method !== 'GET')
216
220
  return reject(405, 'development.connection.read_only');
217
- if (Date.now() >= expiresAt)
218
- return reject(401, 'development.connection.expired');
219
221
  const bearer = request.headers.authorization;
220
222
  if (typeof bearer !== 'string' || !/^Bearer [a-f0-9]{64}$/u.test(bearer)
221
223
  || !timingSafeEqual(Buffer.from(bearer), Buffer.from('Bearer ' + credential))) {
@@ -224,6 +226,16 @@ export async function startDevelopmentWorkspace(options) {
224
226
  if (!request.url?.startsWith('/') || request.url.startsWith('//') || request.url.includes('?')) {
225
227
  return reject(400, 'development.connection.invalid_path');
226
228
  }
229
+ lastActivity = performance.now();
230
+ activeRequests++;
231
+ let releasedActivity = false;
232
+ const activityDone = () => { if (!releasedActivity) {
233
+ releasedActivity = true;
234
+ activeRequests--;
235
+ lastActivity = performance.now();
236
+ } };
237
+ response.once('finish', activityDone);
238
+ response.once('close', activityDone);
227
239
  if (request.url === '/__promptframe/registry') {
228
240
  observe();
229
241
  return json(response, 200, registry());
@@ -234,7 +246,7 @@ export async function startDevelopmentWorkspace(options) {
234
246
  observe();
235
247
  response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-store' });
236
248
  listeners.add(response);
237
- response.on('close', () => listeners.delete(response));
249
+ response.on('close', () => { listeners.delete(response); lastActivity = performance.now(); });
238
250
  response.on('error', () => response.destroy());
239
251
  response.write(`event: registry\ndata: ${JSON.stringify(registry())}\n\n`);
240
252
  return;
@@ -301,8 +313,17 @@ export async function startDevelopmentWorkspace(options) {
301
313
  vite.watcher.on('error', () => { watcherDiagnostic = 'development.workspace.watch_failed'; notify(); });
302
314
  try {
303
315
  await new Promise((resolve, reject) => {
304
- http.once('error', reject);
305
- http.listen(port, host, () => { http.removeListener('error', reject); resolve(); });
316
+ const listen = (candidate, remaining) => {
317
+ const failed = (error) => { http.removeListener('listening', ready); if (error.code === 'EADDRINUSE' && remaining > 0)
318
+ listen(24000 + (candidate - 24000 + 1) % 1000, remaining - 1);
319
+ else
320
+ reject(error); };
321
+ const ready = () => { http.removeListener('error', failed); resolve(); };
322
+ http.once('error', failed);
323
+ http.once('listening', ready);
324
+ http.listen(candidate, host);
325
+ };
326
+ listen(port, options.portFallback ? 999 : 0);
306
327
  });
307
328
  }
308
329
  catch (error) {
@@ -318,35 +339,64 @@ export async function startDevelopmentWorkspace(options) {
318
339
  expectedHost = `${host === '::1' ? '[::1]' : host}:${address.port}`;
319
340
  for (const id of modules.keys())
320
341
  enqueue(id);
321
- const expiry = setTimeout(() => {
322
- controller.abort(new Error('development.connection.expired'));
323
- pending.clear();
324
- for (const response of listeners) {
325
- response.write('event: expired\ndata: {"code":"development.connection.expired"}\n\n');
326
- response.end();
327
- }
328
- listeners.clear();
329
- }, expiresAt - Date.now());
330
- expiry.unref();
331
342
  let closePromise;
332
- return Object.freeze({ connectionId, url: `http://${expectedHost}`, studioOrigin: origin.origin,
333
- credential, expiresAt: new Date(expiresAt).toISOString(),
334
- close() {
335
- return closePromise ??= (async () => {
336
- closed = true;
337
- clearTimeout(expiry);
338
- controller.abort(new Error('development.workspace.closed'));
339
- pending.clear();
340
- for (const response of listeners)
341
- response.end();
342
- listeners.clear();
343
- const httpClosed = new Promise((resolve, reject) => http.close(error => error ? reject(error) : resolve()));
344
- http.closeAllConnections();
343
+ let resolveClosed;
344
+ const closedResult = new Promise(resolve => { resolveClosed = resolve; });
345
+ function close(reason = 'manual') {
346
+ return closePromise ??= (async () => {
347
+ closed = true;
348
+ clearInterval(heartbeat);
349
+ clearInterval(idle);
350
+ controller.abort(new Error('development.workspace.closed'));
351
+ pending.clear();
352
+ for (const response of listeners)
353
+ response.end();
354
+ listeners.clear();
355
+ const httpClosed = new Promise((resolve, reject) => http.close(error => error ? reject(error) : resolve()));
356
+ http.closeAllConnections();
357
+ let firstError;
358
+ try {
345
359
  await pumping;
360
+ }
361
+ catch (error) {
362
+ firstError = error;
363
+ }
364
+ try {
346
365
  await vite.close();
366
+ }
367
+ catch (error) {
368
+ firstError ??= error;
369
+ }
370
+ try {
347
371
  await httpClosed;
348
- })();
349
- },
350
- });
372
+ }
373
+ catch (error) {
374
+ firstError ??= error;
375
+ }
376
+ resolveClosed({ reason, failed: !!firstError });
377
+ if (firstError)
378
+ throw firstError;
379
+ })();
380
+ }
381
+ const heartbeat = setInterval(() => {
382
+ for (const response of listeners) {
383
+ try {
384
+ if (!response.write(': heartbeat\n\n'))
385
+ response.destroy();
386
+ }
387
+ catch {
388
+ response.destroy();
389
+ }
390
+ }
391
+ }, heartbeatMs);
392
+ heartbeat.unref();
393
+ const idle = setInterval(() => {
394
+ if (idleTimeoutMs && !listeners.size && !activeRequests && performance.now() - lastActivity >= idleTimeoutMs)
395
+ void close('idle').catch(() => undefined);
396
+ }, Math.max(10, Math.min(1000, idleTimeoutMs || 1000)));
397
+ idle.unref();
398
+ http.on('error', () => { void close('fatal').catch(() => undefined); });
399
+ return Object.freeze({ connectionId, url: `http://${expectedHost}`, studioOrigin: origin.origin,
400
+ credential, lifecycle, closed: closedResult, close });
351
401
  }
352
402
  //# sourceMappingURL=development-workspace.js.map