@twinsearth/w2m-dsh-plugin 0.57.1 → 0.58.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/CHANGELOG.md +45 -0
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/src/chatops/ilink-fleet-bridge.mjs +67 -1
- package/src/plugin/tools.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,51 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.58.0] — 2026-10-15
|
|
8
|
+
|
|
9
|
+
**The bridge now reports what it received instead of guessing what an image looks like.**
|
|
10
|
+
|
|
11
|
+
### Why this is a probe and not an image branch
|
|
12
|
+
|
|
13
|
+
`ITEM_TYPE` declares exactly one member:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
export const ITEM_TYPE = Object.freeze({ TEXT: 1 });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**The shape of every other item type is unrecorded in this repository** — no `image_item`, no sample payload, no
|
|
20
|
+
fixture. `ilink-protocol.mjs` documents only the text form. **So a download path written today would be a guess
|
|
21
|
+
about field names, and it would fail at the moment a real photograph arrived** — which is the worst possible time
|
|
22
|
+
to discover a wrong guess, because the person is waiting for their picture to be read.
|
|
23
|
+
|
|
24
|
+
**This is the discovery step.** `describeItems()` reports **key paths and value types, never values**, so one
|
|
25
|
+
photograph sent by a person answers the question the source cannot.
|
|
26
|
+
|
|
27
|
+
### Values are never recorded, and that is the point of the whole function
|
|
28
|
+
|
|
29
|
+
A message body is the user's content, and **a discovery aid that quietly wrote photographs into a log file would
|
|
30
|
+
be a worse defect than the one it exists to diagnose.** One assertion is dedicated to that: a distinctive
|
|
31
|
+
passphrase planted in a URL and a caption, and the check that neither appears in the output.
|
|
32
|
+
|
|
33
|
+
The walk is bounded — `maxKeys` and `maxDepth`, arrays described by length and first element, cycles reported
|
|
34
|
+
rather than followed — **so a surprising payload cannot bury the log it is being written to.**
|
|
35
|
+
|
|
36
|
+
### Every wrong guess this session was corrected by printing the thing
|
|
37
|
+
|
|
38
|
+
A fabricated relay route (`/v1/stats`, `/v1/status`), a malformed PNG reported as a credential failure, a count
|
|
39
|
+
typed from memory onto a CHANGELOG entry — **each was fixed by reading rather than predicting.** This applies that
|
|
40
|
+
method *before* the guess instead of after it, which is the only difference between this and the four previous
|
|
41
|
+
occasions.
|
|
42
|
+
|
|
43
|
+
### What is still not done, stated rather than implied
|
|
44
|
+
|
|
45
|
+
**The bridge still cannot read an image.** The `/v1`-style branch that downloads it does not exist, because the
|
|
46
|
+
field names are unknown. What exists now is the instrument that finds them: **send one photograph, and the log
|
|
47
|
+
says what the far end actually sends.**
|
|
48
|
+
|
|
49
|
+
**13 new tests, 0 failures**, and the existing bridge and tool-card suites still pass — the added `shape` field is
|
|
50
|
+
carried only on a non-text reply, so it means one thing. Full suite 2354 in 89 files. **Pre-flight GREEN.**
|
|
51
|
+
|
|
7
52
|
## [0.57.1] — 2026-10-15
|
|
8
53
|
|
|
9
54
|
**The vision status line now appears only on a bridge that actually starts — which it already did, for a reason
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "DSH: Windows2MacOS — drive the same project on online Windows and macOS machines from one DeepSeek account, across networks and regions. P2P hole punching is the default path (relay + shared server as rendezvous, ledger and fallback), with a bundled STUN responder, Tailscale/public-VPS/tunnel shapes, Rabbit relay + per-machine Localside + eight DSH tools (replicate / split), nine aggregate verdicts and signed requests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -116,13 +116,13 @@
|
|
|
116
116
|
}
|
|
117
117
|
},
|
|
118
118
|
"scripts": {
|
|
119
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/chatops-fleet-join.test.mjs",
|
|
119
|
+
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
|
|
120
120
|
"test:e2e": "node --test --test-force-exit test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/p2p-agent.test.mjs",
|
|
121
121
|
"test:relay": "node --test --test-force-exit test/relay.test.mjs",
|
|
122
122
|
"test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
|
|
123
123
|
"test:tools": "node --test --test-force-exit test/tools.test.mjs",
|
|
124
124
|
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
125
|
-
"test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/chatops-fleet-join.test.mjs",
|
|
125
|
+
"test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs",
|
|
126
126
|
"pack": "node scripts/pack.mjs",
|
|
127
127
|
"verify:join": "node scripts/verify-join.mjs",
|
|
128
128
|
"lint": "eslint .",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "twinsearth-w2m-dsh",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.58.0",
|
|
5
5
|
"description": "Cross-platform fleet execution between Windows and macOS: one DeepSeek Harness account, a Rabbit relay, and signed device identity. Ships four Agent Skills describing fleet dispatch, field-level memory acceptance, session references, and resource scheduling.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -113,6 +113,57 @@ export function textOf(message) {
|
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Describe the SHAPE of a message's items without recording any of their content.
|
|
118
|
+
*
|
|
119
|
+
* ## Why this exists rather than an image branch
|
|
120
|
+
*
|
|
121
|
+
* `ITEM_TYPE` declares exactly one member, `TEXT: 1`. **The shape of every other item type is unrecorded in this
|
|
122
|
+
* repository** -- no `image_item`, no sample payload, no fixture. So a download path written now would be a guess
|
|
123
|
+
* about field names, and **it would fail at the moment a real person's photograph arrived**, which is the worst
|
|
124
|
+
* possible time to discover it.
|
|
125
|
+
*
|
|
126
|
+
* This is the discovery step instead. It reports **key paths and value types, never values**, so an operator can
|
|
127
|
+
* send one photograph and the log then says what the far end actually sends. **Every wrong guess this session
|
|
128
|
+
* was corrected by printing the thing instead of predicting it** -- a fabricated relay route, a malformed PNG, a
|
|
129
|
+
* count on a CHANGELOG entry -- and this is that method applied before the guess rather than after.
|
|
130
|
+
*
|
|
131
|
+
* Values are never included because a message body is the user's content, and **a discovery aid that quietly
|
|
132
|
+
* logs photographs to a file would be a worse defect than the one it is diagnosing.**
|
|
133
|
+
*
|
|
134
|
+
* @param {object} message one `msgs[]` entry
|
|
135
|
+
* @param {{maxKeys?: number, maxDepth?: number}} [options]
|
|
136
|
+
* @returns {string} a compact, content-free description
|
|
137
|
+
*/
|
|
138
|
+
export function describeItems(message, { maxKeys = 24, maxDepth = 3 } = {}) {
|
|
139
|
+
const items = Array.isArray(message?.item_list) ? message.item_list : [];
|
|
140
|
+
if (items.length === 0) return 'item_list is empty or absent';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A bounded walk: enough to answer "which fields exist and what kind are they", and short enough that a
|
|
144
|
+
* surprising payload cannot bury the log. Arrays are described by their first element, because a caption list
|
|
145
|
+
* and a sticker list need the same answer.
|
|
146
|
+
*/
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
const walk = (value, depth) => {
|
|
149
|
+
if (value === null) return 'null';
|
|
150
|
+
if (Array.isArray(value)) return value.length === 0 ? 'array[0]' : `array[${value.length}] of ${walk(value[0], depth)}`;
|
|
151
|
+
const kind = typeof value;
|
|
152
|
+
if (kind !== 'object') return kind;
|
|
153
|
+
if (depth >= maxDepth) return 'object{…}';
|
|
154
|
+
if (seen.has(value)) return 'object{cyclic}';
|
|
155
|
+
seen.add(value);
|
|
156
|
+
const keys = Object.keys(value).slice(0, maxKeys);
|
|
157
|
+
const parts = keys.map((k) => `${k}:${walk(value[k], depth + 1)}`);
|
|
158
|
+
const more = Object.keys(value).length > maxKeys ? `,+${Object.keys(value).length - maxKeys}` : '';
|
|
159
|
+
return `{${parts.join(',')}${more}}`;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return items
|
|
163
|
+
.map((item, i) => `item[${i}].type=${JSON.stringify(item?.type)} ${walk(item, 1)}`)
|
|
164
|
+
.join(' | ');
|
|
165
|
+
}
|
|
166
|
+
|
|
116
167
|
/**
|
|
117
168
|
* The reply envelope.
|
|
118
169
|
*
|
|
@@ -230,7 +281,22 @@ export function planMessage({ message, authorize }) {
|
|
|
230
281
|
// A non-text message is answered rather than dropped: "every sentence must get a reply" includes the
|
|
231
282
|
// sentences the bridge cannot read.
|
|
232
283
|
if (text === null) {
|
|
233
|
-
|
|
284
|
+
const shape = describeItems(message);
|
|
285
|
+
return {
|
|
286
|
+
act: 'reply',
|
|
287
|
+
to: from,
|
|
288
|
+
contextToken,
|
|
289
|
+
text: helpText({ reason: '这条消息不是文字(语音/图片/文件暂不支持)' }),
|
|
290
|
+
/**
|
|
291
|
+
* Carried out of the plan so the caller can log it.
|
|
292
|
+
*
|
|
293
|
+
* **The shape, never the content.** This field exists so that one photograph sent by a person answers the
|
|
294
|
+
* question this file cannot answer from its own source -- which fields iLink actually puts on an image --
|
|
295
|
+
* and it is reported rather than acted on, because acting on a guessed shape is how a download path ends up
|
|
296
|
+
* failing on the first real photograph.
|
|
297
|
+
*/
|
|
298
|
+
shape,
|
|
299
|
+
};
|
|
234
300
|
}
|
|
235
301
|
|
|
236
302
|
const trimmed = text.trim();
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -208,7 +208,7 @@ const MAX_STAGES = 16;
|
|
|
208
208
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
209
209
|
* overrides it at runtime for tests and packagers.
|
|
210
210
|
*/
|
|
211
|
-
const PLUGIN_VERSION = '0.
|
|
211
|
+
const PLUGIN_VERSION = '0.58.0';
|
|
212
212
|
|
|
213
213
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
214
214
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|