@wangmo-bit/ds-link 0.0.0-stage → 0.8.18

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -2
  3. package/TESTING.md +59 -0
  4. package/THIRD_PARTY_NOTICES.md +313 -0
  5. package/assets/icon.svg +7 -0
  6. package/cordis.patch.yml +3 -0
  7. package/dist/client.js +6012 -0
  8. package/package.json +102 -4
  9. package/src/core/constants.js +188 -0
  10. package/src/core/downgrade.js +80 -0
  11. package/src/core/engine.js +4669 -0
  12. package/src/core/errors.js +51 -0
  13. package/src/core/ledger.js +1174 -0
  14. package/src/core/migration.js +741 -0
  15. package/src/core/render.js +61 -0
  16. package/src/core/settings.js +147 -0
  17. package/src/core/validation.js +454 -0
  18. package/src/host/adapter.js +1152 -0
  19. package/src/host/connection.js +113 -0
  20. package/src/host/entry-0.2.0.js +13 -0
  21. package/src/host/entry-0.3.0.js +13 -0
  22. package/src/host/entry-0.4.0.js +13 -0
  23. package/src/host/entry-0.8.10.js +13 -0
  24. package/src/host/entry-0.8.11.js +13 -0
  25. package/src/host/entry-0.8.12.js +13 -0
  26. package/src/host/entry-0.8.13.js +13 -0
  27. package/src/host/entry-0.8.14.js +13 -0
  28. package/src/host/entry-0.8.15.js +13 -0
  29. package/src/host/entry-0.8.16-pre.1.js +13 -0
  30. package/src/host/entry-0.8.16-pre.2.js +13 -0
  31. package/src/host/entry-0.8.16-pre.3.js +13 -0
  32. package/src/host/entry-0.8.16-pre.4.js +13 -0
  33. package/src/host/entry-0.8.16-pre.5.js +13 -0
  34. package/src/host/entry-0.8.18.js +13 -0
  35. package/src/host/entry-0.8.5.js +13 -0
  36. package/src/host/entry-0.8.6.js +13 -0
  37. package/src/host/entry-0.8.7.js +13 -0
  38. package/src/host/entry-0.8.8.js +13 -0
  39. package/src/host/entry-0.8.9.js +13 -0
  40. package/src/host/index.js +74 -0
  41. package/src/host/plugin.js +403 -0
  42. package/src/host/prompt.js +22 -0
  43. package/src/host/tools.js +312 -0
package/package.json CHANGED
@@ -1,6 +1,104 @@
1
1
  {
2
2
  "name": "@wangmo-bit/ds-link",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.8.18",
4
+ "description": "DS-link: local session collaboration with bubble and card views for DeepSeek Harness",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Wangmo-bit/DS-link.git"
8
+ },
9
+ "homepage": "https://github.com/Wangmo-bit/DS-link#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/Wangmo-bit/DS-link/issues"
12
+ },
13
+ "author": "Wangmo-bit",
14
+ "keywords": [
15
+ "deepseek-harness",
16
+ "dsh-plugin",
17
+ "session-collaboration",
18
+ "chat",
19
+ "multi-agent"
20
+ ],
21
+ "icon": "assets/icon.svg",
22
+ "type": "module",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=24",
26
+ "dsh": "0.2.1-rc.5 || 0.2.2-rc.1"
27
+ },
28
+ "exports": {
29
+ ".": "./src/host/entry-0.8.18.js",
30
+ "./runtime-0.8.18": "./src/host/entry-0.8.18.js",
31
+ "./package.json": "./package.json",
32
+ "./client": "./dist/client.js"
33
+ },
34
+ "files": [
35
+ "src/core",
36
+ "src/host",
37
+ "dist",
38
+ "cordis.patch.yml",
39
+ "THIRD_PARTY_NOTICES.md",
40
+ "TESTING.md",
41
+ "assets/icon.svg"
42
+ ],
43
+ "scripts": {
44
+ "test": "node scripts/run-tests.mjs",
45
+ "build": "node scripts/build.mjs",
46
+ "check": "npm run build && npm test",
47
+ "prepack": "node scripts/build.mjs",
48
+ "check:public": "node scripts/check-public.mjs",
49
+ "docs:assets": "node scripts/render-doc-assets.mjs"
50
+ },
51
+ "dsh": {
52
+ "bundle": {
53
+ "patch": "./cordis.patch.yml"
54
+ },
55
+ "client": {
56
+ "platform": "web",
57
+ "inject": [
58
+ "@deepseek-ai/dsh-client-ui-layout",
59
+ "@deepseek-ai/dsh-client-ui-settings",
60
+ "@deepseek-ai/dsh-client-connection",
61
+ "@deepseek-ai/dsh-client-ui-workspace",
62
+ "@deepseek-ai/dsh-api-session-controller",
63
+ "@deepseek-ai/dsh-api-remotes",
64
+ "@deepseek-ai/dsh-client-ui-conversation",
65
+ "@deepseek-ai/dsh-client-ui-chat",
66
+ "@deepseek-ai/dsh-client-ui-primitives",
67
+ "@deepseek-ai/dsh-client-ui-tool",
68
+ "@deepseek-ai/dsh-client-locale"
69
+ ]
70
+ }
71
+ },
72
+ "peerDependencies": {
73
+ "@deepseek-ai/cordis": "~4.0.5 || ~4.1.0",
74
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.5 || 0.2.2-rc.1",
75
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.5 || 0.2.2-rc.1",
76
+ "@deepseek-ai/schemastery": "~3.18.5 || ~3.19.0",
77
+ "react": "^18.2.0"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "@deepseek-ai/cordis": {
81
+ "optional": true
82
+ },
83
+ "@deepseek-ai/dsh-llm": {
84
+ "optional": true
85
+ },
86
+ "@deepseek-ai/dsh-tools": {
87
+ "optional": true
88
+ },
89
+ "@deepseek-ai/schemastery": {
90
+ "optional": true
91
+ },
92
+ "react": {
93
+ "optional": true
94
+ }
95
+ },
96
+ "devDependencies": {
97
+ "@deepseek-ai/cordis": "4.1.0",
98
+ "esbuild": "^0.25.0",
99
+ "playwright-core": "1.64.1",
100
+ "react": "^18.2.0",
101
+ "react-dom": "18.3.1",
102
+ "zod": "4.4.3"
103
+ }
104
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Shared vocabulary and technical formats for dsh-session-link 0.3.
3
+ *
4
+ * The values in LIMITS are protocol/format protections and storage envelopes,
5
+ * never per-day or per-peer business quotas. Owner-editable daily settings are
6
+ * limited to DEFAULT_SETTINGS.
7
+ */
8
+
9
+ export const PLUGIN_NAME = 'session-link'
10
+ export const PLUGIN_VERSION = '0.8.18'
11
+ export const API_VERSION = 3
12
+ export const SOURCE_KIND = 'agent-message'
13
+ export const SOURCE_FORM = 'relay'
14
+
15
+ export const STATE_VERSION = 3
16
+ export const V1_STATE_VERSION = 1
17
+ export const LEDGER_FILE = 'ledger.json'
18
+ export const LOCK_FILE = 'writer.lock'
19
+ export const LOCK_DB_FILE = 'writer.sqlite'
20
+ export const V1_BACKUP_PREFIX = 'ledger.v1-backup.'
21
+ /** Exact pre-upgrade v2 bytes kept before the v2 -> v3 schema conversion. */
22
+ export const V2_BACKUP_PREFIX = 'ledger.v2-backup.'
23
+ /** Exact current v3 bytes kept before an offline v3 -> v2 restore. */
24
+ export const V3_ROLLBACK_BACKUP_PREFIX = 'ledger.v3-before-rollback.'
25
+ export const V2_ROLLBACK_BACKUP_PREFIX = 'ledger.v2-before-rollback.'
26
+
27
+ export const TOOL_NAMES = Object.freeze({
28
+ operation: 'session_link_operation',
29
+ list: 'session_link_list',
30
+ history: 'session_link_history',
31
+ send: 'session_link_send',
32
+ reply: 'session_link_reply',
33
+ status: 'session_link_status',
34
+ control: 'session_link_control',
35
+ create: 'session_link_create',
36
+ })
37
+
38
+ /** Durable settings presented by the settings page (exactly five keys). */
39
+ export const DEFAULT_SETTINGS = Object.freeze({
40
+ enabled: true,
41
+ paused: false,
42
+ maxConcurrent: 3,
43
+ requestTimeoutMs: 30 * 60 * 1000,
44
+ retentionDays: 30,
45
+ })
46
+
47
+ export const SETTINGS_KEYS = Object.freeze(Object.keys(DEFAULT_SETTINGS))
48
+
49
+ /** v1 keys that must be rejected as a whole rather than silently ignored. */
50
+ export const LEGACY_SETTINGS_KEYS = Object.freeze([
51
+ 'autoCollaborate',
52
+ 'maxMessages',
53
+ 'maxHops',
54
+ 'maxTextChars',
55
+ 'emergencyPaused',
56
+ ])
57
+
58
+ export const SETTINGS_BOUNDS = Object.freeze({
59
+ maxConcurrent: { min: 1, max: 10 },
60
+ })
61
+
62
+ /**
63
+ * Technical format limits and storage envelopes. There is deliberately no
64
+ * message-count, chain, pair, window, daily, or per-source quota here.
65
+ */
66
+ export const LIMITS = Object.freeze({
67
+ MAX_REFERENCES: 5,
68
+ MAX_REFERENCE_CHARS: 2000,
69
+ MAX_REFERENCE_SESSION_ID_CHARS: 256,
70
+ MAX_PATHS: 10,
71
+ MAX_PATH_CHARS: 1024,
72
+ /** One message content payload (text + references + paths). */
73
+ MAX_CONTENT_BYTES: 64 * 1024,
74
+ /** One send/reply call as received from the model tool arguments. */
75
+ MAX_CALL_BYTES: 1024 * 1024,
76
+ /** Deterministic display excerpt when a sender omitted the subject. */
77
+ SUBJECT_EXCERPT_CHARS: 120,
78
+ /** Deterministic session-title excerpt derived from the task text. */
79
+ TITLE_EXCERPT_CHARS: 60,
80
+ /** One owner card_lookup batch; a technical page bound, not a quota. */
81
+ MAX_CARD_LOOKUP_IDS: 100,
82
+ /** New-inbound active JSON ledger envelope. */
83
+ MAX_LEDGER_BYTES: 50 * 1024 * 1024,
84
+ /** New-inbound undelivered payload envelope (pending + held + dispatching). */
85
+ MAX_QUEUED_BYTES: 16 * 1024 * 1024,
86
+ MAX_PAGE: 50,
87
+ MAX_SNAPSHOT_PAGE: 100,
88
+ HISTORY_MAX_MESSAGES: 20,
89
+ HISTORY_MAX_CHARS: 16000,
90
+ HISTORY_SNIPPET_CHARS: 500,
91
+ /** Fixed metadata tombstone horizon; body retention is owner-editable. */
92
+ MESSAGE_RETENTION_MS: 90 * 24 * 60 * 60 * 1000,
93
+ OPERATION_RETENTION_MS: 90 * 24 * 60 * 60 * 1000,
94
+ /** operationId timestamp acceptance window. */
95
+ OPERATION_PAST_MS: 24 * 60 * 60 * 1000,
96
+ OPERATION_FUTURE_MS: 5 * 60 * 1000,
97
+ /** Largest instant representable by a JavaScript Date. */
98
+ MAX_DATE_MS: 8_640_000_000_000_000,
99
+ /** Forced dispose waits at most this long before refusing the handoff. */
100
+ DISPOSE_DRAIN_MS: 5000,
101
+ /** Bound one tick's read-only receipt catch-up retries and transfer pass. */
102
+ RECEIPT_RETRY_TARGETS: 16,
103
+ TRANSFER_TARGETS_PER_PASS: 16,
104
+ /** A prepared journal that keeps failing its synchronous move becomes uncertain. */
105
+ TRANSFER_MAX_ATTEMPTS: 3,
106
+ })
107
+
108
+ export const DISPATCH_STATUSES = Object.freeze([
109
+ 'held',
110
+ 'pending',
111
+ 'dispatching',
112
+ 'accepted',
113
+ 'failed',
114
+ 'unknown',
115
+ 'cancelled',
116
+ ])
117
+
118
+ export const REPLY_STATUSES = Object.freeze([
119
+ 'none',
120
+ 'waiting',
121
+ 'answered',
122
+ 'timed_out',
123
+ ])
124
+
125
+ export const MESSAGE_KINDS = Object.freeze(['notification', 'request', 'reply'])
126
+
127
+ /**
128
+ * Durable phases of one create journal. `creating` means an external effect
129
+ * may be in flight; `uncertain` is only set when its result cannot be proven.
130
+ * Terminal phases are eligible for the 90-day operation retention window.
131
+ */
132
+ export const CREATE_PHASES = Object.freeze([
133
+ 'creating',
134
+ 'created',
135
+ 'attach_failed',
136
+ 'renamed',
137
+ 'rename_failed',
138
+ 'sending',
139
+ 'sent',
140
+ 'uncertain',
141
+ 'failed',
142
+ ])
143
+
144
+ /** Phases a completed create operation may never leave again. */
145
+ export const CREATE_TERMINAL_PHASES = Object.freeze(['sent', 'attach_failed', 'failed'])
146
+
147
+ /** Rename attempts are one-shot: a failure is reported, never auto-repeated. */
148
+ export const CREATE_RENAME_STATUSES = Object.freeze(['not_attempted', 'pending', 'renamed', 'rename_failed'])
149
+
150
+ export const CHAIN_STATES = Object.freeze(['open', 'paused', 'closed'])
151
+
152
+ export const SNAPSHOT_FILTERS = Object.freeze([
153
+ 'all',
154
+ 'received',
155
+ 'sent',
156
+ 'pending_reply',
157
+ 'abnormal',
158
+ 'unknown',
159
+ 'held',
160
+ 'chain',
161
+ ])
162
+
163
+ export const CONTROL_ACTIONS = Object.freeze([
164
+ 'cancel',
165
+ 'pause',
166
+ 'resume',
167
+ 'close',
168
+ 'release',
169
+ 'recheck',
170
+ 'resolve_unknown',
171
+ 'resume_budget',
172
+ ])
173
+
174
+ /** Held reasons that are automatic consequences of broad settings. */
175
+ export const AUTO_HELD_REASONS = Object.freeze([
176
+ 'paused',
177
+ 'disabled',
178
+ 'emergency_paused',
179
+ 'auto_collaborate_off',
180
+ 'held_full',
181
+ ])
182
+
183
+ /** Chain pause reasons retired by the v1 -> v2 migration. */
184
+ export const RETIRED_CHAIN_PAUSE_REASONS = Object.freeze([
185
+ 'hop_exceeded',
186
+ 'chain_budget',
187
+ 'budget_latched',
188
+ ])
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Read-only 0.4 -> 0.3 downgrade readiness check for THIS plugin's ledger
3
+ * data. It never mutates, installs, migrates, or deletes anything.
4
+ *
5
+ * Strict full-ledger validation runs FIRST on a structured clone (validators
6
+ * normalize `settings` in place, so the caller's state is never touched), and
7
+ * a malformed or incomplete ledger fails closed instead of being mistaken for
8
+ * "no0.4 constructs". Only a validated state is then checked for the three
9
+ * hard blockers:
10
+ * - `state.creates` (even terminal):0.3 ignores the region, so the same root
11
+ * operationId could be reused by send/reply and break deduplication;
12
+ * - a non-null `message.subject`:0.3 redaction never clears it, changing body
13
+ * retention semantics;
14
+ * - sealed `s2:` operation fingerprints:0.3 recomputes raw canonical JSON and
15
+ * would conflict on a legitimate replay.
16
+ *
17
+ * A clean, validated state only establishes the plugin data-shape necessary
18
+ * condition. It does not claim Host HMR/GUI compatibility, full rollback
19
+ * completeness, or retention safety, and it must not be used to run a
20
+ * production change without the user's explicit consent.
21
+ *
22
+ * This module deliberately lives outside `migration.js`: `ledger.js` imports
23
+ * migration helpers, so importing the ledger validator from migration would
24
+ * create an import cycle.
25
+ * @param {{ currentState?: Record<string, any> } | Record<string, any>} request
26
+ * @returns {{ ok: boolean, reasons: string[], notes?: string[] }}
27
+ */
28
+ import { STATE_VERSION } from './constants.js'
29
+ import { validateV3State } from './ledger.js'
30
+ import { isPlainObject } from './validation.js'
31
+
32
+ export function v04DowngradePreflight(request) {
33
+ const state = request?.currentState ?? request
34
+ if (!isPlainObject(state) || state.version !== STATE_VERSION) {
35
+ return { ok: false, reasons: ['current state is not a v3 ledger'] }
36
+ }
37
+ let candidate
38
+ try {
39
+ candidate = structuredClone(state)
40
+ } catch {
41
+ return { ok: false, reasons: ['current state cannot be cloned for validation'] }
42
+ }
43
+ try {
44
+ validateV3State(candidate)
45
+ } catch (error) {
46
+ const message = error instanceof Error ? error.message : String(error)
47
+ return { ok: false, reasons: [`ledger failed strict validation: ${message.slice(0, 300)}`] }
48
+ }
49
+ const reasons = []
50
+ const creates = isPlainObject(candidate.creates) ? Object.keys(candidate.creates) : []
51
+ if (creates.length > 0) {
52
+ reasons.push(
53
+ `state.creates holds ${creates.length} create journal(s):0.3 ignores this region, so a root operationId could be reused by send/reply and break deduplication (terminal and unknown records included)`,
54
+ )
55
+ }
56
+ const withSubject = candidate.messages.filter(
57
+ (message) => message?.subject !== undefined && message.subject !== null,
58
+ ).length
59
+ if (withSubject > 0) {
60
+ reasons.push(
61
+ `${withSubject} message(s) carry a subject:0.3 redaction never clears it, so body retention semantics differ`,
62
+ )
63
+ }
64
+ const sealed = Object.values(candidate.operations).filter(
65
+ (operation) => typeof operation?.fingerprint === 'string' && operation.fingerprint.startsWith('s2:'),
66
+ ).length
67
+ if (sealed > 0) {
68
+ reasons.push(
69
+ `${sealed} operation record(s) use sealed s2 fingerprints:0.3 recomputes raw canonical JSON and would conflict on a legitimate replay`,
70
+ )
71
+ }
72
+ if (reasons.length > 0) return { ok: false, reasons }
73
+ return {
74
+ ok: true,
75
+ reasons: [],
76
+ notes: [
77
+ 'only the session-link data-shape necessary condition is established; Host HMR/GUI compatibility and full rollback/retention safety are not verified',
78
+ ],
79
+ }
80
+ }