openworkgraph 0.2.12

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.
Files changed (178) hide show
  1. package/README.md +64 -0
  2. package/dist/api-image-agent.d.ts +32 -0
  3. package/dist/api-image-agent.js +132 -0
  4. package/dist/api-image-output.d.ts +8 -0
  5. package/dist/api-image-output.js +41 -0
  6. package/dist/api.d.ts +35 -0
  7. package/dist/api.js +153 -0
  8. package/dist/auth.d.ts +58 -0
  9. package/dist/auth.js +273 -0
  10. package/dist/backend/adapter.d.ts +35 -0
  11. package/dist/backend/adapter.js +439 -0
  12. package/dist/backend/capabilities.d.ts +23 -0
  13. package/dist/backend/capabilities.js +75 -0
  14. package/dist/backend/diagnostics.d.ts +9 -0
  15. package/dist/backend/diagnostics.js +80 -0
  16. package/dist/backend/index.d.ts +6 -0
  17. package/dist/backend/index.js +7 -0
  18. package/dist/backend/models.d.ts +15 -0
  19. package/dist/backend/models.js +43 -0
  20. package/dist/backend/paths.d.ts +6 -0
  21. package/dist/backend/paths.js +69 -0
  22. package/dist/backend/stdio.d.ts +66 -0
  23. package/dist/backend/stdio.js +172 -0
  24. package/dist/backend/types.d.ts +90 -0
  25. package/dist/backend/types.js +11 -0
  26. package/dist/backup-api.d.ts +10 -0
  27. package/dist/backup-api.js +40 -0
  28. package/dist/blob-store.d.ts +40 -0
  29. package/dist/blob-store.js +218 -0
  30. package/dist/cli-help.d.ts +1 -0
  31. package/dist/cli-help.js +63 -0
  32. package/dist/cli-output.d.ts +4 -0
  33. package/dist/cli-output.js +130 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +284 -0
  36. package/dist/client-pairing.d.ts +8 -0
  37. package/dist/client-pairing.js +30 -0
  38. package/dist/directories.d.ts +14 -0
  39. package/dist/directories.js +34 -0
  40. package/dist/errors.d.ts +12 -0
  41. package/dist/errors.js +7 -0
  42. package/dist/events.d.ts +22 -0
  43. package/dist/events.js +155 -0
  44. package/dist/execution-chains.d.ts +24 -0
  45. package/dist/execution-chains.js +231 -0
  46. package/dist/execution-settings.d.ts +10 -0
  47. package/dist/execution-settings.js +38 -0
  48. package/dist/graph-document-history.d.ts +43 -0
  49. package/dist/graph-document-history.js +275 -0
  50. package/dist/graph-files.d.ts +14 -0
  51. package/dist/graph-files.js +59 -0
  52. package/dist/graph-transfer.d.ts +53 -0
  53. package/dist/graph-transfer.js +501 -0
  54. package/dist/graphs.d.ts +68 -0
  55. package/dist/graphs.js +771 -0
  56. package/dist/history.d.ts +20 -0
  57. package/dist/history.js +61 -0
  58. package/dist/http.d.ts +2 -0
  59. package/dist/http.js +42 -0
  60. package/dist/image-credentials.d.ts +11 -0
  61. package/dist/image-credentials.js +130 -0
  62. package/dist/image-providers.d.ts +51 -0
  63. package/dist/image-providers.js +271 -0
  64. package/dist/input-materialization.d.ts +17 -0
  65. package/dist/input-materialization.js +95 -0
  66. package/dist/inputs.d.ts +74 -0
  67. package/dist/inputs.js +301 -0
  68. package/dist/local-control.d.ts +13 -0
  69. package/dist/local-control.js +37 -0
  70. package/dist/network.d.ts +11 -0
  71. package/dist/network.js +48 -0
  72. package/dist/openai-image-driver.d.ts +42 -0
  73. package/dist/openai-image-driver.js +368 -0
  74. package/dist/operations/backups.d.ts +40 -0
  75. package/dist/operations/backups.js +391 -0
  76. package/dist/operations/capacity.d.ts +7 -0
  77. package/dist/operations/capacity.js +35 -0
  78. package/dist/operations/cli-context.d.ts +7 -0
  79. package/dist/operations/cli-context.js +58 -0
  80. package/dist/operations/daemon.d.ts +36 -0
  81. package/dist/operations/daemon.js +159 -0
  82. package/dist/operations/files.d.ts +12 -0
  83. package/dist/operations/files.js +84 -0
  84. package/dist/operations/lifecycle.d.ts +28 -0
  85. package/dist/operations/lifecycle.js +155 -0
  86. package/dist/operations/local-admin.d.ts +6 -0
  87. package/dist/operations/local-admin.js +38 -0
  88. package/dist/operations/logs.d.ts +16 -0
  89. package/dist/operations/logs.js +37 -0
  90. package/dist/operations/plugins.d.ts +10 -0
  91. package/dist/operations/plugins.js +11 -0
  92. package/dist/operations/snapshot-worker.d.ts +1 -0
  93. package/dist/operations/snapshot-worker.js +18 -0
  94. package/dist/operations/snapshot.d.ts +1 -0
  95. package/dist/operations/snapshot.js +14 -0
  96. package/dist/operations/update.d.ts +26 -0
  97. package/dist/operations/update.js +216 -0
  98. package/dist/persistence/asset-business-schema.d.ts +2 -0
  99. package/dist/persistence/asset-business-schema.js +36 -0
  100. package/dist/persistence/database.d.ts +7 -0
  101. package/dist/persistence/database.js +91 -0
  102. package/dist/persistence/plugin-schema.d.ts +9 -0
  103. package/dist/persistence/plugin-schema.js +38 -0
  104. package/dist/persistence/repositories.d.ts +20 -0
  105. package/dist/persistence/repositories.js +106 -0
  106. package/dist/persistence/resource-schema.d.ts +2 -0
  107. package/dist/persistence/resource-schema.js +44 -0
  108. package/dist/persistence/schema.d.ts +6 -0
  109. package/dist/persistence/schema.js +236 -0
  110. package/dist/persistence/workflow-schema.d.ts +2 -0
  111. package/dist/persistence/workflow-schema.js +19 -0
  112. package/dist/plugin-api.d.ts +10 -0
  113. package/dist/plugin-api.js +40 -0
  114. package/dist/plugin-management.d.ts +36 -0
  115. package/dist/plugin-management.js +112 -0
  116. package/dist/plugins.d.ts +55 -0
  117. package/dist/plugins.js +239 -0
  118. package/dist/project-discovery.d.ts +3 -0
  119. package/dist/project-discovery.js +57 -0
  120. package/dist/project-files-api.d.ts +16 -0
  121. package/dist/project-files-api.js +243 -0
  122. package/dist/project-files.d.ts +97 -0
  123. package/dist/project-files.js +557 -0
  124. package/dist/project-lock.d.ts +4 -0
  125. package/dist/project-lock.js +35 -0
  126. package/dist/project-probe-worker.d.ts +1 -0
  127. package/dist/project-probe-worker.js +17 -0
  128. package/dist/project-probe.d.ts +22 -0
  129. package/dist/project-probe.js +67 -0
  130. package/dist/projects.d.ts +29 -0
  131. package/dist/projects.js +219 -0
  132. package/dist/publication.d.ts +31 -0
  133. package/dist/publication.js +642 -0
  134. package/dist/resource-api.d.ts +15 -0
  135. package/dist/resource-api.js +256 -0
  136. package/dist/resources-orphans.d.ts +41 -0
  137. package/dist/resources-orphans.js +235 -0
  138. package/dist/resources.d.ts +246 -0
  139. package/dist/resources.js +714 -0
  140. package/dist/run-api.d.ts +10 -0
  141. package/dist/run-api.js +308 -0
  142. package/dist/run-context.d.ts +54 -0
  143. package/dist/run-context.js +137 -0
  144. package/dist/runs.d.ts +82 -0
  145. package/dist/runs.js +506 -0
  146. package/dist/runtime-backend.d.ts +24 -0
  147. package/dist/runtime-backend.js +214 -0
  148. package/dist/runtime.d.ts +97 -0
  149. package/dist/runtime.js +553 -0
  150. package/dist/scheduler.d.ts +85 -0
  151. package/dist/scheduler.js +344 -0
  152. package/dist/service.d.ts +29 -0
  153. package/dist/service.js +515 -0
  154. package/dist/skills-api.d.ts +18 -0
  155. package/dist/skills-api.js +120 -0
  156. package/dist/storage-api.d.ts +21 -0
  157. package/dist/storage-api.js +92 -0
  158. package/dist/transfer-api.d.ts +10 -0
  159. package/dist/transfer-api.js +60 -0
  160. package/dist/vendor/protocol/contracts.typecheck.d.ts +1 -0
  161. package/dist/vendor/protocol/contracts.typecheck.js +35 -0
  162. package/dist/vendor/protocol/execution-chain.d.ts +6 -0
  163. package/dist/vendor/protocol/execution-chain.js +26 -0
  164. package/dist/vendor/protocol/graph-binary.d.ts +15 -0
  165. package/dist/vendor/protocol/graph-binary.js +121 -0
  166. package/dist/vendor/protocol/graph-title.d.ts +2 -0
  167. package/dist/vendor/protocol/graph-title.js +23 -0
  168. package/dist/vendor/protocol/index.d.ts +617 -0
  169. package/dist/vendor/protocol/index.js +20 -0
  170. package/dist/vendor/protocol/pairing.d.ts +29 -0
  171. package/dist/vendor/protocol/pairing.js +71 -0
  172. package/dist/vendor/protocol/preview.d.ts +2 -0
  173. package/dist/vendor/protocol/preview.js +9 -0
  174. package/dist/vendor/protocol/resource-mime.d.ts +2 -0
  175. package/dist/vendor/protocol/resource-mime.js +35 -0
  176. package/dist/video-thumbnail.d.ts +2 -0
  177. package/dist/video-thumbnail.js +34 -0
  178. package/package.json +9 -0
package/dist/auth.js ADDED
@@ -0,0 +1,273 @@
1
+ import { createHash, randomBytes, randomUUID, randomInt } from 'node:crypto';
2
+ import { PAIR_AUTHORIZATION_PREFIX, encodePairAuthorization, parsePairAuthorization, normalizeShortPairingCode, formatShortPairingCode } from "./vendor/protocol/index.js";
3
+ import { inspectClientCode, requirePairingProof } from './client-pairing.js';
4
+ import { ServiceError } from './errors.js';
5
+ import { transaction } from './persistence/database.js';
6
+ import { Repositories } from './persistence/repositories.js';
7
+ export const SESSION_IDLE_MS = 30 * 24 * 60 * 60 * 1000;
8
+ export const PAIRING_TTL_MS = 5 * 60 * 1000;
9
+ export function normalizeOrigin(value) {
10
+ try {
11
+ const url = new URL(value);
12
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hostname.includes('*') || url.pathname !== '/' || url.search || url.hash || url.origin === 'null')
13
+ throw new Error();
14
+ return url.origin;
15
+ }
16
+ catch {
17
+ throw new ServiceError('ORIGIN_DENIED', '网页来源必须是完整的 http(s)://主机:端口,不含路径、账号、查询或通配符。');
18
+ }
19
+ }
20
+ export class Auth {
21
+ db;
22
+ serviceId;
23
+ now;
24
+ pendingClients = new Map();
25
+ shortAttempts = new Map();
26
+ prunePairingRequests() {
27
+ for (const [key, value] of this.pendingClients)
28
+ if (value.expiresAt <= this.now())
29
+ this.pendingClients.delete(key);
30
+ for (const [key, value] of this.shortAttempts)
31
+ if (value.expiresAt <= this.now())
32
+ this.shortAttempts.delete(key);
33
+ }
34
+ /** Public registration grants no trust. Only the local control channel can resolve/approve it. */
35
+ requestClientPairing(clientCode, origin) {
36
+ const { client } = inspectClientCode(clientCode);
37
+ if (client.origin !== origin)
38
+ throw new ServiceError('ORIGIN_DENIED', '客户端来源与当前网页不匹配。');
39
+ this.prunePairingRequests();
40
+ for (const [code, pending] of this.pendingClients)
41
+ if (pending.clientCode === clientCode)
42
+ return { code: formatShortPairingCode(code), expiresAt: new Date(pending.expiresAt).toISOString(), serviceId: this.serviceId };
43
+ if (this.pendingClients.size >= 100)
44
+ throw new ServiceError('INVALID_REQUEST', '待配对请求过多,请五分钟后重试。');
45
+ let code;
46
+ do {
47
+ code = randomInt(100_000_000).toString().padStart(8, '0');
48
+ } while (this.pendingClients.has(code));
49
+ const expiresAt = this.now() + PAIRING_TTL_MS;
50
+ this.pendingClients.set(code, { clientCode, expiresAt });
51
+ return { code: formatShortPairingCode(code), expiresAt: new Date(expiresAt).toISOString(), serviceId: this.serviceId };
52
+ }
53
+ resolveClientPairing(code) {
54
+ this.prunePairingRequests();
55
+ const pending = this.pendingClients.get(normalizeShortPairingCode(code) ?? '');
56
+ if (!pending)
57
+ throw new ServiceError('PAIRING_CODE_INVALID', '客户端码无效或已过期,请在浏览器重新生成。');
58
+ return pending.clientCode;
59
+ }
60
+ approveClientPairing(code, clientCode) {
61
+ if (this.resolveClientPairing(code) !== clientCode)
62
+ throw new ServiceError('PAIRING_CODE_INVALID', '客户端请求已变化,请重新确认。');
63
+ const requestCode = normalizeShortPairingCode(code);
64
+ const pending = this.pendingClients.get(requestCode);
65
+ const { client, fingerprint } = inspectClientCode(clientCode);
66
+ const origin = normalizeOrigin(client.origin);
67
+ const approvedAt = this.now();
68
+ pending.approvedAt = approvedAt;
69
+ return { status: 'approved', origin, fingerprint, expiresAt: new Date(pending.expiresAt).toISOString(), serviceId: this.serviceId };
70
+ }
71
+ claimClientPairing(input, origin) {
72
+ if (input.expectedServiceId !== this.serviceId)
73
+ throw new ServiceError('SERVICE_MISMATCH', '服务身份不一致,请核对客户端码对应的运行时。');
74
+ if (typeof input.browserName !== 'string' || !input.browserName.trim() || input.browserName.trim().length > 100 || /[\x00-\x1f\x7f]/.test(input.browserName))
75
+ throw new ServiceError('INVALID_REQUEST', '浏览器名称必须为 1~100 个字符,不能包含控制字符。');
76
+ const code = typeof input.code === 'string' ? normalizeShortPairingCode(input.code) : undefined;
77
+ if (!code || typeof input.clientCode !== 'string')
78
+ throw new ServiceError('PAIRING_CODE_INVALID', '客户端码无效,请在浏览器重新生成。');
79
+ this.prunePairingRequests();
80
+ const pending = this.pendingClients.get(code);
81
+ if (!pending || pending.clientCode !== input.clientCode)
82
+ throw new ServiceError('PAIRING_CODE_INVALID', '客户端码无效或已过期,请在浏览器重新生成。');
83
+ const inspected = inspectClientCode(input.clientCode);
84
+ if (inspected.client.origin !== origin)
85
+ throw new ServiceError('ORIGIN_DENIED', '客户端来源与当前网页不匹配。');
86
+ requirePairingProof(input.clientCode, input.proof, this.serviceId, origin, input.code, input.browserName);
87
+ if (!pending.approvedAt)
88
+ return { status: 'pending', expiresAt: new Date(pending.expiresAt).toISOString(), serviceId: this.serviceId };
89
+ if (pending.response) {
90
+ if (pending.browserName !== input.browserName.trim())
91
+ throw new ServiceError('PAIRING_CODE_INVALID', '客户端名称与已领取的会话不匹配。');
92
+ return { status: pending.completedAt ? 'paired' : 'ready', expiresAt: new Date(pending.expiresAt).toISOString(), ...pending.response };
93
+ }
94
+ const now = this.now();
95
+ const token = 'wg1_' + randomBytes(32).toString('base64url');
96
+ const id = randomUUID();
97
+ const browserName = input.browserName.trim();
98
+ const response = { serviceId: this.serviceId, token, session: this.toSession({ id, browser_name: browserName, paired_at: now, last_used_at: now, revoked_at: null, origin }, id) };
99
+ pending.browserName = browserName;
100
+ pending.response = response;
101
+ return { status: 'ready', expiresAt: new Date(pending.expiresAt).toISOString(), ...response };
102
+ }
103
+ completeClientPairing(codeInput, expectedServiceId, token, origin) {
104
+ if (expectedServiceId !== this.serviceId)
105
+ throw new ServiceError('SERVICE_MISMATCH', '服务身份不一致,请核对客户端码对应的运行时。');
106
+ const code = normalizeShortPairingCode(codeInput);
107
+ this.prunePairingRequests();
108
+ const pending = code ? this.pendingClients.get(code) : undefined;
109
+ if (!pending?.approvedAt || !pending.response || inspectClientCode(pending.clientCode).client.origin !== origin || this.hash('session', token) !== this.hash('session', pending.response.token))
110
+ throw new ServiceError('PAIRING_CODE_INVALID', '配对握手无效或已过期,请重新生成客户端码。');
111
+ if (!pending.completedAt) {
112
+ const session = pending.response.session;
113
+ transaction(this.db, () => {
114
+ this.db.prepare('INSERT OR IGNORE INTO trusted_origins(origin,created_at) VALUES(?,?)').run(origin, pending.approvedAt);
115
+ this.db.prepare('INSERT INTO sessions(id,token_hash,browser_name,paired_at,last_used_at,origin) VALUES(?,?,?,?,?,?)').run(session.id, this.hash('session', token), session.browserName, Date.parse(session.pairedAt), Date.parse(session.lastUsedAt), origin);
116
+ });
117
+ pending.completedAt = this.now();
118
+ }
119
+ return { status: 'paired', ...pending.response };
120
+ }
121
+ constructor(db, serviceId, now = Date.now) {
122
+ this.db = db;
123
+ this.serviceId = serviceId;
124
+ this.now = now;
125
+ }
126
+ hash(kind, value) { return createHash('sha256').update(this.serviceId + ':' + kind + ':' + value).digest('hex'); }
127
+ /** Local-only authority. Never route issuance through the network API. */
128
+ issueCode(originInput) {
129
+ const origin = normalizeOrigin(originInput);
130
+ const code = randomBytes(16).toString('hex').toUpperCase();
131
+ const now = this.now();
132
+ transaction(this.db, () => {
133
+ this.db.prepare('INSERT OR IGNORE INTO trusted_origins(origin,created_at) VALUES(?,?)').run(origin, now);
134
+ this.db.prepare('INSERT INTO pairing_codes(code_hash,expires_at,origin) VALUES(?,?,?)').run(this.hash('pair', code), now + PAIRING_TTL_MS, origin);
135
+ });
136
+ return { code: code.match(/.{8}/g).join('-'), origin, expiresAt: new Date(now + PAIRING_TTL_MS).toISOString(), serviceId: this.serviceId };
137
+ }
138
+ /** Local authorization only. A bound grant can never use the legacy exchange. */
139
+ issueClientCode(clientCode, short = false) {
140
+ const { client, fingerprint, clientHash } = inspectClientCode(clientCode);
141
+ const origin = normalizeOrigin(client.origin), now = this.now();
142
+ const secret = short ? randomInt(100_000_000).toString().padStart(8, '0') : randomBytes(16).toString('hex').toUpperCase();
143
+ const expiresAt = now + PAIRING_TTL_MS;
144
+ transaction(this.db, () => {
145
+ this.db.prepare('INSERT OR IGNORE INTO trusted_origins(origin,created_at) VALUES(?,?)').run(origin, now);
146
+ if (short)
147
+ this.db.prepare('DELETE FROM pairing_codes WHERE client_code=?').run(clientCode);
148
+ this.db.prepare('INSERT INTO pairing_codes(code_hash,expires_at,origin,client_code) VALUES(?,?,?,?)').run(this.hash('pair', short ? clientHash + ':' + secret : secret), expiresAt, origin, clientCode);
149
+ });
150
+ this.shortAttempts.delete(clientHash);
151
+ return { code: short ? formatShortPairingCode(secret) : encodePairAuthorization({ version: 1, serviceId: this.serviceId, code: secret, clientHash, expiresAt }), origin, fingerprint, expiresAt: new Date(expiresAt).toISOString(), serviceId: this.serviceId };
152
+ }
153
+ requireOrigin(origin) {
154
+ if (!origin || normalizeOrigin(origin) !== origin || !this.db.prepare('SELECT 1 FROM trusted_origins WHERE origin=?').get(origin))
155
+ throw new ServiceError('ORIGIN_DENIED', '此客户端来源尚未由本机授权;请先在浏览器生成客户端码,再在服务设备运行 pair --client-code 后重试。');
156
+ return origin;
157
+ }
158
+ pair(input, origin) {
159
+ this.requireOrigin(origin);
160
+ if (input.expectedServiceId !== this.serviceId)
161
+ throw new ServiceError('SERVICE_MISMATCH', '服务身份不一致,请核对本机命令输出的 serviceId 和地址。');
162
+ if (typeof input.browserName !== 'string' || !input.browserName.trim() || input.browserName.trim().length > 100 || /[\x00-\x1f\x7f]/.test(input.browserName))
163
+ throw new ServiceError('INVALID_REQUEST', '浏览器名称必须为 1~100 个字符,不能包含控制字符。');
164
+ let grant;
165
+ const short = typeof input.code === 'string' ? normalizeShortPairingCode(input.code) : undefined;
166
+ let shortHash;
167
+ if (short) {
168
+ if (typeof input.clientCode !== 'string')
169
+ throw new ServiceError('PAIRING_CODE_INVALID', '请在生成客户端码的原页面配对。');
170
+ // Verify possession before counting attempts, so strangers cannot lock out a client.
171
+ requirePairingProof(input.clientCode, input.proof, this.serviceId, origin, input.code, input.browserName);
172
+ const clientHash = inspectClientCode(input.clientCode).clientHash;
173
+ this.prunePairingRequests();
174
+ let attempts = this.shortAttempts.get(clientHash);
175
+ if (!attempts) {
176
+ if (this.shortAttempts.size >= 1000)
177
+ throw new ServiceError('PAIRING_CODE_INVALID', '配对请求过多,请稍后重试。');
178
+ attempts = { count: 0, expiresAt: this.now() + PAIRING_TTL_MS };
179
+ this.shortAttempts.set(clientHash, attempts);
180
+ }
181
+ if (attempts.count >= 5)
182
+ throw new ServiceError('PAIRING_CODE_INVALID', '尝试次数过多,请重新生成客户端码并在服务设备授权。');
183
+ attempts.count++;
184
+ shortHash = this.hash('pair', clientHash + ':' + short);
185
+ }
186
+ if (typeof input.code === 'string' && input.code.startsWith(PAIR_AUTHORIZATION_PREFIX)) {
187
+ try {
188
+ grant = parsePairAuthorization(input.code);
189
+ }
190
+ catch {
191
+ throw new ServiceError('PAIRING_CODE_INVALID', '配对授权码无效,请复制完整授权码。');
192
+ }
193
+ if (grant.serviceId !== this.serviceId)
194
+ throw new ServiceError('SERVICE_MISMATCH', '授权码属于其他服务。');
195
+ }
196
+ else if (!short && (typeof input.code !== 'string' || !/^(?:[a-fA-F0-9]{32}|[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{8}){3})$/.test(input.code)))
197
+ throw new ServiceError('PAIRING_CODE_INVALID', '配对码无效,请输入 8 位数字。');
198
+ const hash = shortHash ?? this.hash('pair', grant?.code ?? input.code.replaceAll('-', '').toUpperCase());
199
+ return transaction(this.db, () => {
200
+ const code = this.db.prepare('SELECT expires_at,consumed_at,origin,client_code FROM pairing_codes WHERE code_hash=?').get(hash);
201
+ const now = this.now();
202
+ if (!code)
203
+ throw new ServiceError('PAIRING_CODE_INVALID', '配对码无效,请在目标服务设备重新获取。');
204
+ if (code['origin'] !== origin)
205
+ throw new ServiceError('ORIGIN_DENIED', '配对码绑定了其他网页来源,请按当前网页来源重新获取。');
206
+ if (code['consumed_at'] !== null)
207
+ throw new ServiceError('PAIRING_CODE_USED', '配对码已兑换;若浏览器未保存成功,请在服务设备重新获取配对码。');
208
+ if (now >= Number(code['expires_at']))
209
+ throw new ServiceError('PAIRING_CODE_EXPIRED', '配对码已超过 5 分钟,请在服务设备重新获取。');
210
+ if (code['client_code'] !== null) {
211
+ if (typeof input.clientCode !== 'string' || input.clientCode !== code['client_code'] || (!short && (!grant || grant.expiresAt !== code['expires_at'] || grant.clientHash !== inspectClientCode(input.clientCode).clientHash)))
212
+ throw new ServiceError('PAIRING_CODE_INVALID', '授权码与此客户端不匹配;不能降级兑换,请在原页面重试。');
213
+ requirePairingProof(input.clientCode, input.proof, this.serviceId, origin, input.code, input.browserName);
214
+ }
215
+ else if (grant || input.clientCode !== undefined || input.proof !== undefined) {
216
+ throw new ServiceError('PAIRING_CODE_INVALID', '此授权格式与本机签发记录不匹配。');
217
+ }
218
+ const token = 'wg1_' + randomBytes(32).toString('base64url');
219
+ const id = randomUUID();
220
+ this.db.prepare('UPDATE pairing_codes SET consumed_at=? WHERE code_hash=? AND consumed_at IS NULL').run(now, hash);
221
+ this.db.prepare('INSERT INTO sessions(id,token_hash,browser_name,paired_at,last_used_at,origin) VALUES(?,?,?,?,?,?)').run(id, this.hash('session', token), input.browserName.trim(), now, now, origin);
222
+ return { serviceId: this.serviceId, token, session: this.toSession({ id, browser_name: input.browserName.trim(), paired_at: now, last_used_at: now, revoked_at: null, origin }, id) };
223
+ });
224
+ }
225
+ active(token, origin) {
226
+ this.requireOrigin(origin);
227
+ if (!/^wg1_[A-Za-z0-9_-]{43}$/.test(token))
228
+ throw new ServiceError('UNAUTHENTICATED', '需要有效的 Bearer 会话凭据;请重新配对。');
229
+ const row = this.db.prepare('SELECT id,browser_name,origin,paired_at,last_used_at,revoked_at FROM sessions WHERE token_hash=?').get(this.hash('session', token));
230
+ if (!row)
231
+ throw new ServiceError('UNAUTHENTICATED', '会话不存在或不属于此服务;请重新配对。');
232
+ if (row.origin !== origin)
233
+ throw new ServiceError('ORIGIN_DENIED', '会话仅允许从配对时的网页来源访问。');
234
+ if (row.revoked_at !== null)
235
+ throw new ServiceError('SESSION_REVOKED', '此浏览器会话已撤销,请重新配对;已接受任务不受影响。');
236
+ if (this.now() >= row.last_used_at + SESSION_IDLE_MS)
237
+ throw new ServiceError('SESSION_EXPIRED', '此会话已连续 30 天未使用,请重新配对。');
238
+ return row;
239
+ }
240
+ /** Recheck immediately before a stream emits data; keepalives must not renew idle expiry. */
241
+ assertActive(token, origin) { this.active(token, origin); }
242
+ /** Authorization, renewal and each short operation share a transaction, including CLI revocation races. */
243
+ withSession(token, origin, operation) {
244
+ return transaction(this.db, () => {
245
+ const row = this.active(token, origin);
246
+ row.last_used_at = Math.max(this.now(), row.last_used_at);
247
+ this.db.prepare('UPDATE sessions SET last_used_at=? WHERE id=?').run(row.last_used_at, row.id);
248
+ return operation(this.toSession(row, row.id));
249
+ });
250
+ }
251
+ list(currentId) {
252
+ const rows = this.db.prepare('SELECT id,browser_name,origin,paired_at,last_used_at,revoked_at FROM sessions WHERE revoked_at IS NULL ORDER BY paired_at,id').all();
253
+ return rows.map(row => this.toSession(row, currentId));
254
+ }
255
+ /** Call in the authenticated operation transaction, or through revokeLocal. */
256
+ revoke(id) {
257
+ if (!this.db.isTransaction)
258
+ throw new Error('Revocation requires a transaction');
259
+ const row = this.db.prepare('SELECT revoked_at FROM sessions WHERE id=?').get(id);
260
+ if (!row)
261
+ throw new ServiceError('NOT_FOUND', '会话不存在,请刷新浏览器列表。');
262
+ if (row['revoked_at'] !== null)
263
+ return;
264
+ const now = this.now();
265
+ this.db.prepare('UPDATE sessions SET revoked_at=? WHERE id=?').run(now, id);
266
+ new Repositories(this.db).appendEvent({ eventId: randomUUID(), type: 'session.revoked', projectId: null, graphId: null, entityId: id, revision: 1, occurredAt: new Date(now).toISOString(), payload: { sessionId: id } });
267
+ }
268
+ revokeLocal(id) { transaction(this.db, () => this.revoke(id)); }
269
+ toSession(row, currentId) {
270
+ return { id: row.id, browserName: row.browser_name, origin: row.origin, pairedAt: new Date(row.paired_at).toISOString(), lastUsedAt: new Date(row.last_used_at).toISOString(), expiresAt: new Date(row.last_used_at + SESSION_IDLE_MS).toISOString(), current: row.id === currentId, state: this.now() >= row.last_used_at + SESSION_IDLE_MS ? 'expired' : 'active' };
271
+ }
272
+ }
273
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,35 @@
1
+ import { type BackendAdapter, type BackendCallbacks, type BackendReply, type BackendRunContext, type RuntimeSnapshot } from './types.js';
2
+ export interface AdapterOptions {
3
+ executable?: string;
4
+ args?: string[];
5
+ timeoutMs?: number;
6
+ initializeTimeoutMs?: number;
7
+ }
8
+ /** References are accepted Run data, not instructions from the service or browser. */
9
+ export declare function referenceGuidance(context: BackendRunContext): string;
10
+ export declare function upstreamGuidance(context: BackendRunContext): string;
11
+ export declare function executionDeliveryGuidance(): string;
12
+ /** Fail closed for path/credential-bearing messages. Never pass tool output or reason here.
13
+ * Inspect the whole message before bounding it; partial deltas are not safe to publish. */
14
+ export declare function publicProgressText(value: unknown): string;
15
+ /** One process and fresh ephemeral thread per Run. No DB, auth mutation, Git, or fallback. */
16
+ export declare class CodexBackendAdapter implements BackendAdapter {
17
+ private readonly options;
18
+ private sessions;
19
+ constructor(options?: AdapterOptions);
20
+ private get;
21
+ private snapshot;
22
+ private emit;
23
+ private unknown;
24
+ start(context: BackendRunContext, callbacks: BackendCallbacks): Promise<RuntimeSnapshot>;
25
+ private incoming;
26
+ private message;
27
+ private request;
28
+ respond(runId: string, interactionId: string, reply: BackendReply): Promise<RuntimeSnapshot>;
29
+ cancel(runId: string): Promise<RuntimeSnapshot>;
30
+ reconcile(runId: string): Promise<RuntimeSnapshot>;
31
+ inspect(runId: string): RuntimeSnapshot;
32
+ /** Call only after durable terminal finalization. Unknown Runs are never silently forgotten. */
33
+ forget(runId: string): void;
34
+ close(): void;
35
+ }