termdock 1.4.316 → 1.4.318
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/dist/server/agent/collaborationPrompt.js +1 -1
- package/dist/server/agent/collaborationProtocol.js +2 -0
- package/dist/server/agent/collaborationStore.js +50 -3
- package/dist/server/agent/integrationCli.js +30 -0
- package/dist/server/agent/integrationServer.js +9 -0
- package/docs/integrations.md +69 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
|
@@ -23,7 +23,7 @@ export function collaborationRulesAge(updatedAt, now = Date.now()) {
|
|
|
23
23
|
* names may carry them, so the render layer neutralizes them defensively;
|
|
24
24
|
* user-entered names are rejected at creation instead.
|
|
25
25
|
*/
|
|
26
|
-
export
|
|
26
|
+
export { COLLAB_NAME_FORBIDDEN } from './collaborationProtocol.js';
|
|
27
27
|
/** Render-safe name for shell header / per-message source lines: reserved
|
|
28
28
|
* bracket and delimiter characters become spaces (they would otherwise split
|
|
29
29
|
* the `「X」群` and `来自:X · kind` lines), control characters are
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
/** Shared local group-name validation; presentation keeps its compatible export. */
|
|
3
|
+
export const COLLAB_NAME_FORBIDDEN = /[【】「」·\x00-\x1f\x7f]/;
|
|
2
4
|
/** Transport facts and application reports deliberately remain separate. */
|
|
3
5
|
export const COLLAB_LIMITS = {
|
|
4
6
|
message_bytes: 1_048_576,
|
|
@@ -3,7 +3,7 @@ import { validateLaunchProfiles } from './collaborationLaunchProfiles.js';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
|
-
import { COLLAB_LIMITS, CollaborationError, STATUS_RANK, canonicalShortId, drawCollaborationId, newCollaborationId, resolveIdPrefix, validateExtras } from './collaborationProtocol.js';
|
|
6
|
+
import { COLLAB_NAME_FORBIDDEN, COLLAB_LIMITS, CollaborationError, STATUS_RANK, canonicalShortId, drawCollaborationId, newCollaborationId, resolveIdPrefix, validateExtras } from './collaborationProtocol.js';
|
|
7
7
|
function newerRevision(a, b) {
|
|
8
8
|
return !b || a.updatedAt > b.updatedAt || (a.updatedAt === b.updatedAt && a.version > b.version);
|
|
9
9
|
}
|
|
@@ -64,9 +64,10 @@ export class CollaborationStore {
|
|
|
64
64
|
activity_observed_at: typeof observedAt === 'number' && Number.isFinite(observedAt) ? observedAt : null,
|
|
65
65
|
last_peer_sync_at: Date.now(), activity_source: 'remote_terminal_output' });
|
|
66
66
|
}
|
|
67
|
-
document = { version: 2, groups: [], messages: [], terminalHistoryComplete: true };
|
|
67
|
+
document = { version: 2, groups: [], messages: [], terminalHistoryComplete: true, localGroupRequestsComplete: true };
|
|
68
68
|
persistedDocument = JSON.stringify(this.document);
|
|
69
69
|
historyAvailable = true;
|
|
70
|
+
groupCreationAvailable = true;
|
|
70
71
|
/** `drawId` is the random source behind newCollaborationId. It is a
|
|
71
72
|
* constructor seam so tests can force a collision and prove the guard
|
|
72
73
|
* below actually skips it; production always uses the CSPRNG default. */
|
|
@@ -94,6 +95,14 @@ export class CollaborationStore {
|
|
|
94
95
|
this.document.sequence = Math.max(this.document.sequence ?? 0, message.sequence);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
98
|
+
const requests = parsed.localGroupRequests;
|
|
99
|
+
this.groupCreationAvailable = [1, 2].includes(parsed.version ?? 0) && Array.isArray(parsed.groups)
|
|
100
|
+
&& (parsed.localGroupRequestsComplete === undefined || parsed.localGroupRequestsComplete === true) && (requests === undefined || requests !== null && typeof requests === 'object' && !Array.isArray(requests)
|
|
101
|
+
&& Object.entries(requests).every(([key, value]) => /^[a-f0-9]{64}$/.test(key) && value && /^[a-f0-9]{64}$/.test(value.hash)
|
|
102
|
+
&& value.group && typeof value.group.id === 'string' && typeof value.group.name === 'string'
|
|
103
|
+
&& Array.isArray(value.group.sessionIds) && value.group.sessionIds.length === 0 && value.group.federated === false
|
|
104
|
+
&& Number.isSafeInteger(value.group.createdAt) && Number.isSafeInteger(value.group.updatedAt)));
|
|
105
|
+
this.document.localGroupRequests = this.groupCreationAvailable ? requests ?? {} : {};
|
|
97
106
|
this.document.sequence = Math.max(parsed.sequence ?? 0, this.document.sequence ?? 0);
|
|
98
107
|
this.document.idempotency = parsed.idempotency ?? {};
|
|
99
108
|
this.document.consumers = parsed.consumers ?? {};
|
|
@@ -104,8 +113,10 @@ export class CollaborationStore {
|
|
|
104
113
|
}
|
|
105
114
|
catch (error) {
|
|
106
115
|
this.historyAvailable = error.code === 'ENOENT';
|
|
116
|
+
this.groupCreationAvailable = this.historyAvailable;
|
|
107
117
|
}
|
|
108
118
|
this.document.terminalHistoryComplete = this.historyAvailable;
|
|
119
|
+
this.document.localGroupRequestsComplete = this.groupCreationAvailable;
|
|
109
120
|
this.persistedDocument = JSON.stringify(this.document);
|
|
110
121
|
}
|
|
111
122
|
list() {
|
|
@@ -170,6 +181,42 @@ export class CollaborationStore {
|
|
|
170
181
|
this.persist();
|
|
171
182
|
return group;
|
|
172
183
|
}
|
|
184
|
+
/** The administrator request and empty local group share one durable commit.
|
|
185
|
+
* No terminal identity, arbitrary membership, or federation is involved. */
|
|
186
|
+
createEmptyLocalGroup(value, key) {
|
|
187
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 256 || COLLAB_NAME_FORBIDDEN.test(value)) {
|
|
188
|
+
throw new CollaborationError('INVALID_GROUP', 'name must be non-empty text up to 256 characters without forbidden group-name characters', 400);
|
|
189
|
+
}
|
|
190
|
+
if (typeof key !== 'string' || !key.trim() || key.length > 256)
|
|
191
|
+
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', 'An explicit 1..256 character idempotency key is required', 400);
|
|
192
|
+
if (!this.groupCreationAvailable)
|
|
193
|
+
throw new CollaborationError('GROUP_CREATION_STATE_UNCONFIRMED', 'Creation journal is unavailable; reconcile the persisted state before creating another group', 409);
|
|
194
|
+
const name = value.trim(), requestKey = crypto.createHash('sha256').update(key).digest('hex');
|
|
195
|
+
const hash = crypto.createHash('sha256').update(JSON.stringify(['empty-local-group', name])).digest('hex');
|
|
196
|
+
const previous = this.document.localGroupRequests?.[requestKey];
|
|
197
|
+
if (previous) {
|
|
198
|
+
if (previous.hash !== hash)
|
|
199
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', 'This administrator key was used for different group parameters', 409);
|
|
200
|
+
const current = this.getGroup(previous.group.id);
|
|
201
|
+
if (!current || current.deleted || current.federated)
|
|
202
|
+
throw new CollaborationError('GROUP_CREATE_TARGET_UNAVAILABLE', 'Original local group was deleted or changed; this key cannot create a replacement', 409);
|
|
203
|
+
// A previous directory-fsync failure may have committed the key without
|
|
204
|
+
// confirming durability. Reconfirm the current group+key before ACK.
|
|
205
|
+
this.persist(true);
|
|
206
|
+
return structuredClone(previous.group);
|
|
207
|
+
}
|
|
208
|
+
if (Object.keys(this.document.localGroupRequests ?? {}).length >= 2000)
|
|
209
|
+
throw new CollaborationError('GROUP_CREATE_LIMIT', 'Administrator group creation journal has reached its 2000-record limit', 409);
|
|
210
|
+
const now = Date.now(), group = { id: newCollaborationId(this.takenIds(), this.drawId), name,
|
|
211
|
+
sessionIds: [], federated: false, createdAt: now, updatedAt: now };
|
|
212
|
+
this.document = { ...this.document, groups: [...this.document.groups, group], localGroupRequests: {
|
|
213
|
+
...this.document.localGroupRequests, [requestKey]: { hash, group: structuredClone(group) }
|
|
214
|
+
} };
|
|
215
|
+
// persist restores the last committed document on failure. A post-rename
|
|
216
|
+
// fsync failure retains the committed request so retries cannot duplicate.
|
|
217
|
+
this.persist(true);
|
|
218
|
+
return structuredClone(group);
|
|
219
|
+
}
|
|
173
220
|
setMemberLaunchProfile(groupId, sessionId, profile) {
|
|
174
221
|
const group = this.getGroup(groupId);
|
|
175
222
|
if (!group || group.deleted || !group.sessionIds.includes(sessionId))
|
|
@@ -326,7 +373,7 @@ export class CollaborationStore {
|
|
|
326
373
|
clear() {
|
|
327
374
|
if (this.document.groups.length === 0 && this.document.messages.length === 0)
|
|
328
375
|
return;
|
|
329
|
-
this.document = { version: 2, groups: this.document.groups
|
|
376
|
+
this.document = { version: 2, localGroupRequests: this.document.localGroupRequests, localGroupRequestsComplete: this.groupCreationAvailable, groups: this.document.groups
|
|
330
377
|
.map((group) => group.deleted ? group : ({ ...group, deleted: true, updatedAt: Math.max(Date.now(), group.updatedAt + 1) })), messages: [] };
|
|
331
378
|
this.persist();
|
|
332
379
|
}
|
|
@@ -8,6 +8,18 @@ import { COLLAB_HELP, executeCollaborationCommand, duration } from './collaborat
|
|
|
8
8
|
import { getTermdockVersion } from '../utils/version.js';
|
|
9
9
|
export const INTEGRATION_HELP = `td integration — local scoped integration administration
|
|
10
10
|
groups | list
|
|
11
|
+
group create --name <name> --idempotency-key <key>
|
|
12
|
+
Administrator only; create an empty local group without a terminal source.
|
|
13
|
+
Minimum CLI/running service 1.4.318; admin_local_group_create=true.
|
|
14
|
+
Returns {group: original creation snapshot}; same key/parameters reuse it.
|
|
15
|
+
Groups may later gain members; the snapshot is not their current membership.
|
|
16
|
+
No IDs, members, federation, launch profiles or principal credentials accepted.
|
|
17
|
+
Deleted/changed targets are not recreated; preserve the key after uncertain results.
|
|
18
|
+
Errors: INVALID_GROUP, INVALID_IDEMPOTENCY_KEY, IDEMPOTENCY_CONFLICT,
|
|
19
|
+
GROUP_CREATE_TARGET_UNAVAILABLE, GROUP_CREATION_STATE_UNCONFIRMED,
|
|
20
|
+
GROUP_CREATE_LIMIT (2000 retained creation records), ADMIN_LOCAL_GROUP_CREATE_UNSUPPORTED.
|
|
21
|
+
Next authorize a new fixed-group principal using integration create; no empty
|
|
22
|
+
administrator terminal is required. Existing principal/session ownership is unchanged.
|
|
11
23
|
clear-startup-input <TD-session-id> --generation <N>
|
|
12
24
|
Administrator removes this session's saved screen conditions and disables
|
|
13
25
|
inheritance on later restores; original identity/launch/history are preserved.
|
|
@@ -89,6 +101,24 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
89
101
|
return 0;
|
|
90
102
|
}
|
|
91
103
|
const socketPath = integrationSocketPath(port);
|
|
104
|
+
if (action === 'group') {
|
|
105
|
+
const args = {};
|
|
106
|
+
const usage = 'Usage: td integration group create --name <name> --idempotency-key <key>';
|
|
107
|
+
if (argv[1] !== 'create')
|
|
108
|
+
throw new CollaborationError('INVALID_GROUP_CREATE_REQUEST', usage, 400);
|
|
109
|
+
for (let i = 2; i < argv.length; i += 2) {
|
|
110
|
+
if (!['--name', '--idempotency-key'].includes(argv[i]) || !argv[i + 1] || Object.hasOwn(args, argv[i]))
|
|
111
|
+
throw new CollaborationError('INVALID_GROUP_CREATE_REQUEST', usage, 400);
|
|
112
|
+
args[argv[i]] = argv[i + 1];
|
|
113
|
+
}
|
|
114
|
+
if (!args['--name']?.trim() || !args['--idempotency-key']?.trim() || Object.keys(args).length !== 2)
|
|
115
|
+
throw new CollaborationError('INVALID_GROUP_CREATE_REQUEST', usage, 400);
|
|
116
|
+
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
117
|
+
if (capabilities.admin_local_group_create !== true)
|
|
118
|
+
throw new CollaborationError('ADMIN_LOCAL_GROUP_CREATE_UNSUPPORTED', 'CLI and running service must support empty local group creation (minimum 1.4.318)', 409);
|
|
119
|
+
write(JSON.stringify(parsed(await request(socketPath, 'POST', '/admin/groups', { name: args['--name'], idempotency_key: args['--idempotency-key'] }, headers(undefined, adminToken)))));
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
92
122
|
if (action === 'clear-startup-input') {
|
|
93
123
|
const id = argv[1];
|
|
94
124
|
if (argv.length !== 4 || !id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id) || argv[2] !== '--generation'
|
|
@@ -69,6 +69,14 @@ export class IntegrationRuntime {
|
|
|
69
69
|
integrationError('INTEGRATION_ADMIN_REQUIRED', 'Local administration credential is required', 401);
|
|
70
70
|
};
|
|
71
71
|
app.get('/admin/groups', run(req => { admin(req); return { groups: options.messages.list().filter(g => !g.federated).map(g => ({ id: g.id, name: g.name })) }; }));
|
|
72
|
+
app.post('/admin/groups', run(req => {
|
|
73
|
+
admin(req);
|
|
74
|
+
const body = req.body;
|
|
75
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(key => !['name', 'idempotency_key'].includes(key))) {
|
|
76
|
+
integrationError('INVALID_GROUP_CREATE_REQUEST', 'Specify only name and idempotency_key; members, federation, IDs and launch configuration are not accepted');
|
|
77
|
+
}
|
|
78
|
+
return { group: options.messages.createEmptyLocalGroup(body.name, body.idempotency_key) };
|
|
79
|
+
}));
|
|
72
80
|
app.get('/admin/capabilities', run(req => { admin(req); return this.capabilities(); }));
|
|
73
81
|
app.get('/admin/sessions/:id/restore-diagnostics', run(req => { admin(req); return { ...this.sessions.restoreDiagnostics(req.params.id), ...this.sessions.launchDiagnostics(req.params.id) }; }));
|
|
74
82
|
app.get('/admin/sessions/:id/resume-configuration', run(async (req) => { admin(req); return this.sessions.resumeConfiguration(req.params.id); }));
|
|
@@ -233,6 +241,7 @@ export class IntegrationRuntime {
|
|
|
233
241
|
session_cold_retry: true, session_cold_retry_min_version: '1.4.314',
|
|
234
242
|
session_rename: true, session_rename_min_version: '1.4.315',
|
|
235
243
|
session_close: true, session_close_min_version: '1.4.315',
|
|
244
|
+
admin_local_group_create: true, admin_local_group_create_min_version: '1.4.318',
|
|
236
245
|
integration_policy_update: true,
|
|
237
246
|
session_restore_diagnostics: true,
|
|
238
247
|
native_identity_linux_flock_owner: process.platform === 'linux',
|
package/docs/integrations.md
CHANGED
|
@@ -1026,3 +1026,72 @@ TD 生命周期锁不排他控制外部 tmux 命令/手工进程替换,最终
|
|
|
1026
1026
|
永久审计存储。close 不增加 task、看板或 accepted。真实私有 TraeX 与 DevBox
|
|
1027
1027
|
共享升级/业务适配由接入方后续验收;维护侧验证仅覆盖隔离测试的真实 tmux/Linux
|
|
1028
1028
|
进程退出与源码回归。macOS 真实 close 路径尚未实机验收。
|
|
1029
|
+
|
|
1030
|
+
## 1.4.317 / 1.4.318:管理员幂等创建空本机组
|
|
1031
|
+
|
|
1032
|
+
最低 **CLI / 实际服务均为 1.4.318**,协议仍为 1。管理员回读
|
|
1033
|
+
`admin_local_group_create=true`、`admin_local_group_create_min_version=1.4.318`;
|
|
1034
|
+
旧服务缺少能力时 CLI 返回 `ADMIN_LOCAL_GROUP_CREATE_UNSUPPORTED`,不回退到终端
|
|
1035
|
+
成员来源、网页裸 HTTP 或私有文件写入。
|
|
1036
|
+
|
|
1037
|
+
```sh
|
|
1038
|
+
td integration group create --name 'Case A' --idempotency-key case-a:group
|
|
1039
|
+
# 返回 {"group":{"id":"...","name":"Case A","sessionIds":[],
|
|
1040
|
+
# "federated":false,"createdAt":...,"updatedAt":...}}
|
|
1041
|
+
td integration groups # 回读现有本机组的id/name
|
|
1042
|
+
# 使用返回的group.id填入新的独立policy.groupId:
|
|
1043
|
+
td integration create --file case-a-policy.json --credential-file case-a-credential.json
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
本机 Unix socket 管理路由 `POST /admin/groups`,JSON 只接受
|
|
1047
|
+
`{name,idempotency_key}`。沿用本机管理员 credential 的鉴权与协议版本检查,由正式
|
|
1048
|
+
CLI 从服务状态读取管理员凭据;不要把它放在命令行、消息、日志,也不要发给 Agent。
|
|
1049
|
+
principal 凭据不能调用此入口(`INTEGRATION_ADMIN_REQUIRED`),没有新增 principal
|
|
1050
|
+
建组权限。请求不能携带成员/目标ID/federated/launchProfiles/principal 等字段,
|
|
1051
|
+
即使 `sessionIds=[]` 也属于多余参数,返回 `INVALID_GROUP_CREATE_REQUEST`。
|
|
1052
|
+
|
|
1053
|
+
名称去除首尾空白,非空、最多 256 字符,沿用现有本机组的禁止字符规则(括号
|
|
1054
|
+
`【】「」`、间隔号 `·`、C0 控制字符及 DEL)。名称不作为唯一标识;不同 key 同名可以
|
|
1055
|
+
建立不同组。幂等 key 必须非空、最多 256 字符,按原字符串区分;存储中只保存其哈希。
|
|
1056
|
+
API 名称非法返回 `INVALID_GROUP`,key 非法返回 `INVALID_IDEMPOTENCY_KEY`。
|
|
1057
|
+
|
|
1058
|
+
创建记录和空组写入同一个协作存储文件,以一次 durable 原子替换提交,包含文件与
|
|
1059
|
+
父目录 fsync。服务内同步执行,无终端/tmux/Agent 启动和正文投递;不包含 principal
|
|
1060
|
+
授权、业务调度或三模型创建。**组创建与后续 principal 授权是两步操作,不是一个事务**。
|
|
1061
|
+
后者继续遵循已有凭据落盘、唯一 principal ID、固定授权组和拒绝静默替换规则。
|
|
1062
|
+
|
|
1063
|
+
key在同一服务的管理员入口范围内共享,宿主应使用稳定的业务前缀避免互相碰撞。
|
|
1064
|
+
同服务存储中,同 key 同参数(名称规范化后)返回原创建快照,包括原 ID、空成员和
|
|
1065
|
+
原时间;同 key 异参返回 `IDEMPOTENCY_CONFLICT`。之后给原组加入成员或改名,不会改变
|
|
1066
|
+
创建快照,不能把重试响应的空 `sessionIds` 当作当前成员清单;当前名称可用 groups
|
|
1067
|
+
回读,成员清单沿现有组管理读接口。HTTP 回执丢失或持久写入结果不明时,保留原 key
|
|
1068
|
+
和名称重试对账,不要换新 key “再建一个”。
|
|
1069
|
+
|
|
1070
|
+
1.4.318 补齐异常重试:同键回读前再次 durable 提交当前组和原创建记录;目录
|
|
1071
|
+
fsync 持续失败仍返回失败,不以缓存记录冒充成功。文件替换前失败不会提交半个组;替换后 fsync/回执异常可能已经创建,应同键对账。
|
|
1072
|
+
服务重启保留 key→原快照映射。原组被明确删除、变成跨服务目标或原 ID 已不存在时,
|
|
1073
|
+
同键返回 `GROUP_CREATE_TARGET_UNAVAILABLE`,不新建替代组;全局清理也保留该 key 的
|
|
1074
|
+
保留记录。创建 journal 损坏、无法可靠读取或其完整性未确认返回
|
|
1075
|
+
`GROUP_CREATION_STATE_UNCONFIRMED`,普通存储写入不会自动把这种情况改称“已确认”。
|
|
1076
|
+
此时管理员先对账/修复可信存储,不能通过换 key 绕过。
|
|
1077
|
+
|
|
1078
|
+
每个服务存储最多保留 2,000 个此入口的创建记录(含已删除组的 key 保留记录),满时
|
|
1079
|
+
新 key 返回 `GROUP_CREATE_LIMIT`;已有 key 仍可回读。首版无 TTL/forget,不为腾空间
|
|
1080
|
+
静默删除幂等证据。这不增加会话、普通消息、事件 journal 的原保留额度;调用者负责
|
|
1081
|
+
组/凭据/逻辑记录预算。保证范围是唯一运行服务写入同一持久存储;不提供多个独立
|
|
1082
|
+
服务共享文件的分布式事务,不能降级到旧写入器后再假设幂等证据仍完整。
|
|
1083
|
+
|
|
1084
|
+
推荐外部宿主按自身业务创建独立本机组,再用现有 integration create 授权独立
|
|
1085
|
+
principal,可复用相同的 launchProfiles 配置模板。已有组允许零活动成员,随后
|
|
1086
|
+
principal create/restore 不要求保留空管理终端。此能力不创建业务 Case、定时器、
|
|
1087
|
+
consumer、task 或看板任务;Case/组/principal/session/message 映射由宿主持久保存。
|
|
1088
|
+
|
|
1089
|
+
已有 principal 的 groupId 仍不可修改(`INTEGRATION_GROUP_IMMUTABLE`);普通成员迁组
|
|
1090
|
+
也不会迁移 IntegrationSession 的归属、旧消息/reply、事件或 ACK 游标。此入口不提供
|
|
1091
|
+
旧 Case/原会话迁移。新组独立使用新授权,已有链路保持原归属完成/关闭。
|
|
1092
|
+
|
|
1093
|
+
事件订阅协议未改变:新 consumer 从该组保留窗口最早可见位置开始,并非“从现在”
|
|
1094
|
+
开始;读取不自动 ACK。当前没有公开 ready/subscribed 帧,进程启动不能当订阅完成。
|
|
1095
|
+
即使普通 send 早于实际订阅建立,保留期内的事实也可重放;宿主按 event_id 持久
|
|
1096
|
+
去重/连续 ACK,并按原 message ID/reply_to 用历史对账明确回复。heartbeat 仅是连接
|
|
1097
|
+
观测,不表示宿主 inbox 已落盘或业务已完成。
|
package/package.json
CHANGED
package/runtime-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.318",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
8
|
"dependencyHash": "sha256-oaGYi/P5ob8sd6VnNBOwshKozjhdV+hYiCmLAsn5x00=",
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
9
|
+
"serverBundleHash": "sha256-VznpfJGhg8kZE56U0vZ/UHTcclRkZSaSmlA9EkgtaEg=",
|
|
10
10
|
"clientBundleHash": "sha256-iwZyAmz8pDZptAaDUwHHnAqZlIvu6R9xpWhvQhPz3xY=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|