martty 0.2.16 → 0.2.17

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.
@@ -0,0 +1,70 @@
1
+ /** Host-side directory of installed package entries for the separate TUI Client tree. */
2
+
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { Service } from '@deepseek-ai/cordis'
6
+
7
+ export const name = 'tui-client-plugin-registry'
8
+ export const inject = []
9
+
10
+ const ID = /^[a-z0-9][a-z0-9-]*$/
11
+ const KINDS = new Set(['theme', 'ui-preset'])
12
+
13
+ function normalizeEntry(value) {
14
+ if (typeof value !== 'string' || value.length === 0) {
15
+ throw new Error('tuiClientPlugins.register: entry must be an absolute path or file URL')
16
+ }
17
+ if (path.isAbsolute(value)) return pathToFileURL(value).href
18
+ let url
19
+ try {
20
+ url = new URL(value)
21
+ } catch {
22
+ throw new Error('tuiClientPlugins.register: entry must be an absolute path or file URL')
23
+ }
24
+ if (url.protocol !== 'file:') {
25
+ throw new Error('tuiClientPlugins.register: entry must use the file protocol')
26
+ }
27
+ return url.href
28
+ }
29
+
30
+ class TuiClientPluginsService extends Service {
31
+ constructor(ctx) {
32
+ super(ctx, 'tuiClientPlugins')
33
+ this.entries = new Map()
34
+ }
35
+
36
+ register(options) {
37
+ if (options === null || typeof options !== 'object' || Array.isArray(options)) {
38
+ throw new Error('tuiClientPlugins.register: options must be an object')
39
+ }
40
+ if (typeof options.id !== 'string' || !ID.test(options.id)) {
41
+ throw new Error('tuiClientPlugins.register: id must be a lowercase package identifier')
42
+ }
43
+ if (!KINDS.has(options.kind)) {
44
+ throw new Error('tuiClientPlugins.register: kind must be "theme" or "ui-preset"')
45
+ }
46
+ if (this.entries.has(options.id)) {
47
+ throw new Error(`tuiClientPlugins.register: id ${JSON.stringify(options.id)} is already registered`)
48
+ }
49
+ const entry = {
50
+ id: options.id,
51
+ kind: options.kind,
52
+ entry: normalizeEntry(options.entry),
53
+ }
54
+ this.entries.set(entry.id, entry)
55
+ let disposed = false
56
+ return () => {
57
+ if (disposed) return
58
+ disposed = true
59
+ if (this.entries.get(entry.id) === entry) this.entries.delete(entry.id)
60
+ }
61
+ }
62
+
63
+ list() {
64
+ return [...this.entries.values()].map((entry) => ({ ...entry }))
65
+ }
66
+ }
67
+
68
+ export function apply(ctx) {
69
+ return new TuiClientPluginsService(ctx)
70
+ }
@@ -0,0 +1,213 @@
1
+ /** Merge installed package entries and Creator-authored TUI Client plugins. */
2
+
3
+ import { applyClientHalf, applyClientPlugin, createClientTimer } from './client-run.js'
4
+
5
+ const LOCAL_PREFIX = 'tui-local:'
6
+ const PACKAGE_PREFIX = 'tui-package:'
7
+
8
+ /**
9
+ * @param {object} ctx
10
+ * @param {{
11
+ * store: object,
12
+ * packages?: Array<{ id: string, kind: 'theme' | 'ui-preset', entry: string }>,
13
+ * tuiTheme?: object,
14
+ * tuiPresets?: object,
15
+ * tuiSlots?: object,
16
+ * tuiCommands?: object,
17
+ * tuiOverlay?: object,
18
+ * acpSessionConfig?: object,
19
+ * acpSessionPlan?: object,
20
+ * acpSessionStats?: object,
21
+ * acpSessionStatus?: object,
22
+ * }} options
23
+ */
24
+ export function installTuiLocalPlugins(ctx, options) {
25
+ const { store } = options
26
+ if (store === undefined || typeof store.list !== 'function' || typeof store.resolve !== 'function') {
27
+ throw new Error('tuiLocalPlugins: a durable store is required')
28
+ }
29
+ const records = new Map()
30
+ const failures = []
31
+ const timer = createClientTimer()
32
+ let discovered = false
33
+ let disposed = false
34
+
35
+ const env = (pluginId) => ({
36
+ pluginId,
37
+ tuiTheme: options.tuiTheme,
38
+ tuiPresets: options.tuiPresets,
39
+ tuiSlots: options.tuiSlots,
40
+ tuiCommands: options.tuiCommands,
41
+ tuiOverlay: options.tuiOverlay,
42
+ acpSessionConfig: options.acpSessionConfig,
43
+ acpSessionPlan: options.acpSessionPlan,
44
+ acpSessionStats: options.acpSessionStats,
45
+ acpSessionStatus: options.acpSessionStatus,
46
+ timer,
47
+ })
48
+
49
+ async function mount(record) {
50
+ let applied
51
+ if (record.entry !== undefined) {
52
+ record.module ??= await import(record.entry)
53
+ const plugin = typeof record.module.apply === 'function'
54
+ ? record.module
55
+ : record.module.default
56
+ applied = await applyClientPlugin(plugin, env(record.pluginId))
57
+ } else {
58
+ applied = await applyClientHalf(record.artifact.code.client, env(record.pluginId))
59
+ }
60
+ if (applied.waitingFor.length > 0) {
61
+ applied.dispose()
62
+ throw new Error(`waiting for unavailable service(s): ${applied.waitingFor.join(', ')}`)
63
+ }
64
+ record.release = applied.dispose
65
+ record.loaded = true
66
+ }
67
+
68
+ async function recover(record) {
69
+ records.set(record.pluginId, record)
70
+ try {
71
+ await mount(record)
72
+ if (record.kind === 'theme') {
73
+ record.release?.()
74
+ record.release = undefined
75
+ record.loaded = false
76
+ }
77
+ return true
78
+ } catch (error) {
79
+ record.release?.()
80
+ records.delete(record.pluginId)
81
+ failures.push({
82
+ artifactId: record.artifactId,
83
+ message: error instanceof Error ? error.message : String(error),
84
+ })
85
+ return false
86
+ }
87
+ }
88
+
89
+ async function discover() {
90
+ if (disposed) throw new Error('tuiLocalPlugins: registry is disposed')
91
+ if (discovered) return
92
+ discovered = true
93
+ const installedIds = new Set()
94
+ for (const entry of options.packages ?? []) {
95
+ installedIds.add(entry.id)
96
+ await recover({
97
+ artifactId: entry.id,
98
+ pluginId: `${PACKAGE_PREFIX}${entry.id}`,
99
+ kind: entry.kind,
100
+ entry: entry.entry,
101
+ module: undefined,
102
+ loaded: false,
103
+ release: undefined,
104
+ })
105
+ }
106
+ for (const row of store.list()) {
107
+ if (installedIds.has(row.id)) {
108
+ failures.push({
109
+ artifactId: row.id,
110
+ message: 'Creator artifact is shadowed by an installed package with the same id',
111
+ })
112
+ continue
113
+ }
114
+ if (row.broken !== undefined) {
115
+ failures.push({ artifactId: row.id, message: row.broken })
116
+ continue
117
+ }
118
+ let artifact
119
+ try {
120
+ artifact = store.resolve(row.id)
121
+ } catch (error) {
122
+ failures.push({
123
+ artifactId: row.id,
124
+ message: error instanceof Error ? error.message : String(error),
125
+ })
126
+ continue
127
+ }
128
+ const pluginId = `${LOCAL_PREFIX}${artifact.id}`
129
+ const record = {
130
+ artifact,
131
+ artifactId: artifact.id,
132
+ pluginId,
133
+ kind: artifact.kind,
134
+ loaded: false,
135
+ release: undefined,
136
+ }
137
+ await recover(record)
138
+ }
139
+ const preferredTheme = options.tuiTheme?.preferred?.()
140
+ const preferredOwner = typeof preferredTheme === 'string'
141
+ ? options.tuiTheme?.owner?.(preferredTheme)
142
+ : undefined
143
+ if (typeof preferredOwner === 'string' && records.has(preferredOwner)) {
144
+ try {
145
+ await start(preferredOwner)
146
+ options.tuiTheme.activate(preferredTheme)
147
+ } catch (error) {
148
+ failures.push({
149
+ artifactId: records.get(preferredOwner)?.artifactId ?? preferredOwner,
150
+ message: `failed to restore preferred theme: ${error instanceof Error ? error.message : String(error)}`,
151
+ })
152
+ }
153
+ }
154
+ }
155
+
156
+ async function start(pluginId) {
157
+ if (disposed) throw new Error('tuiLocalPlugins: registry is disposed')
158
+ const record = records.get(pluginId)
159
+ if (record === undefined) throw new Error(`tuiLocalPlugins: unknown Plugin ${JSON.stringify(pluginId)}`)
160
+ if (record.loaded) return
161
+ await mount(record)
162
+ }
163
+
164
+ async function stop(pluginId) {
165
+ const record = records.get(pluginId)
166
+ if (record === undefined || !record.loaded) return false
167
+ record.release?.()
168
+ record.release = undefined
169
+ record.loaded = false
170
+ return true
171
+ }
172
+
173
+ function has(pluginId) {
174
+ return records.has(pluginId)
175
+ }
176
+
177
+ function isLoaded(pluginId) {
178
+ return records.get(pluginId)?.loaded === true
179
+ }
180
+
181
+ function list() {
182
+ return [...records.values()].map((record) => ({
183
+ artifactId: record.artifactId,
184
+ pluginId: record.pluginId,
185
+ kind: record.kind,
186
+ loaded: record.loaded,
187
+ }))
188
+ }
189
+
190
+ function diagnostics() {
191
+ return failures.map((failure) => ({ ...failure }))
192
+ }
193
+
194
+ async function dispose() {
195
+ if (disposed) return
196
+ disposed = true
197
+ for (const record of [...records.values()].reverse()) {
198
+ record.release?.()
199
+ record.release = undefined
200
+ record.loaded = false
201
+ }
202
+ records.clear()
203
+ }
204
+
205
+ const service = { discover, start, stop, has, isLoaded, list, diagnostics, dispose }
206
+ if (typeof ctx.provide === 'function') ctx.provide('tuiLocalPlugins', service)
207
+ if (typeof ctx.effect === 'function') {
208
+ ctx.effect(() => () => service.dispose(), 'tui-local-plugins')
209
+ } else {
210
+ ctx.tuiLocalPlugins = service
211
+ }
212
+ return service
213
+ }
@@ -0,0 +1,197 @@
1
+ /** Durable user-authored TUI Client plugins. */
2
+
3
+ import {
4
+ cpSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ renameSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ } from 'node:fs'
13
+ import { homedir } from 'node:os'
14
+ import path from 'node:path'
15
+
16
+ const ARTIFACT_ID = /^[a-z0-9][a-z0-9-]*$/
17
+ const KINDS = new Set(['theme', 'ui-preset'])
18
+ const MANIFEST = 'plugin.json'
19
+
20
+ function artifactId(value) {
21
+ if (typeof value !== 'string' || !ARTIFACT_ID.test(value)) {
22
+ throw new Error('tuiPluginStore: id must be a lowercase artifact identifier')
23
+ }
24
+ return value
25
+ }
26
+
27
+ function nonEmpty(value, field) {
28
+ if (typeof value !== 'string' || value.trim().length === 0) {
29
+ throw new Error(`tuiPluginStore: ${field} must be a non-empty string`)
30
+ }
31
+ return value
32
+ }
33
+
34
+ function validateManifest(value, expectedId) {
35
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
36
+ throw new Error('manifest must be an object')
37
+ }
38
+ if (value.schemaVersion !== 0) throw new Error('schemaVersion must be 0')
39
+ const id = artifactId(value.id)
40
+ if (id !== expectedId) throw new Error(`manifest id must match directory ${JSON.stringify(expectedId)}`)
41
+ if (!KINDS.has(value.kind)) throw new Error('kind must be "theme" or "ui-preset"')
42
+ const name = nonEmpty(value.name, 'name')
43
+ const purpose = nonEmpty(value.purpose, 'purpose')
44
+ if (value.source === null || typeof value.source !== 'object' || Array.isArray(value.source)) {
45
+ throw new Error('source must be an object')
46
+ }
47
+ const pluginId = nonEmpty(value.source.pluginId, 'source.pluginId')
48
+ const packageId = nonEmpty(value.source.packageId, 'source.packageId')
49
+ if (value.code === null || typeof value.code !== 'object' || Array.isArray(value.code)) {
50
+ throw new Error('code must be an object')
51
+ }
52
+ const client = nonEmpty(value.code.client, 'code.client')
53
+ return {
54
+ schemaVersion: 0,
55
+ id,
56
+ kind: value.kind,
57
+ name,
58
+ purpose,
59
+ source: { pluginId, packageId },
60
+ code: { client },
61
+ }
62
+ }
63
+
64
+ function readManifest(file, id) {
65
+ let parsed
66
+ try {
67
+ parsed = JSON.parse(readFileSync(file, 'utf8'))
68
+ } catch (error) {
69
+ throw new Error(`invalid JSON: ${error instanceof Error ? error.message : String(error)}`)
70
+ }
71
+ return validateManifest(parsed, id)
72
+ }
73
+
74
+ export function tuiPluginRoot(env = process.env) {
75
+ return path.join(marttyHome(env), 'plugins')
76
+ }
77
+
78
+ export function marttyHome(env = process.env, userHome = homedir()) {
79
+ if (typeof env.MARTTY_HOME === 'string' && env.MARTTY_HOME.length > 0) {
80
+ return env.MARTTY_HOME
81
+ }
82
+ if (typeof env.DSH_HOME === 'string' && env.DSH_HOME.length > 0) {
83
+ return path.join(env.DSH_HOME, '.martty')
84
+ }
85
+ return path.join(userHome, '.martty')
86
+ }
87
+
88
+ export function legacyTuiPluginRoot(env = process.env, userHome = homedir()) {
89
+ const dshHome = typeof env.DSH_HOME === 'string' && env.DSH_HOME.length > 0
90
+ ? env.DSH_HOME
91
+ : path.join(userHome, '.dsh')
92
+ return path.join(dshHome, '.tui-plugins')
93
+ }
94
+
95
+ /**
96
+ * @param {{ root?: string }} [options]
97
+ */
98
+ export function createTuiPluginStore(options = {}) {
99
+ const root = options.root ?? tuiPluginRoot()
100
+ if (typeof root !== 'string' || root.length === 0 || !path.isAbsolute(root)) {
101
+ throw new Error('tuiPluginStore: root must be an absolute path')
102
+ }
103
+ const legacyRoot = options.legacyRoot
104
+ ?? (options.root === undefined ? legacyTuiPluginRoot() : undefined)
105
+ if (legacyRoot !== undefined && legacyRoot !== root && existsSync(legacyRoot)) {
106
+ mkdirSync(root, { recursive: true })
107
+ for (const entry of readdirSync(legacyRoot, { withFileTypes: true })) {
108
+ if (!entry.isDirectory() || !ARTIFACT_ID.test(entry.name)) continue
109
+ const source = path.join(legacyRoot, entry.name)
110
+ const target = path.join(root, entry.name)
111
+ if (!existsSync(target)) cpSync(source, target, { recursive: true, errorOnExist: true })
112
+ }
113
+ }
114
+
115
+ const paths = (id) => {
116
+ const safe = artifactId(id)
117
+ const dir = path.join(root, safe)
118
+ return { dir, file: path.join(dir, MANIFEST) }
119
+ }
120
+
121
+ function list() {
122
+ if (!existsSync(root)) return []
123
+ return readdirSync(root, { withFileTypes: true })
124
+ .filter((entry) => entry.isDirectory() && ARTIFACT_ID.test(entry.name))
125
+ .sort((left, right) => left.name.localeCompare(right.name))
126
+ .map((entry) => {
127
+ const { file } = paths(entry.name)
128
+ try {
129
+ const artifact = readManifest(file, entry.name)
130
+ return {
131
+ id: artifact.id,
132
+ kind: artifact.kind,
133
+ name: artifact.name,
134
+ purpose: artifact.purpose,
135
+ path: file,
136
+ broken: undefined,
137
+ }
138
+ } catch (error) {
139
+ return {
140
+ id: entry.name,
141
+ kind: undefined,
142
+ name: entry.name,
143
+ purpose: '',
144
+ path: file,
145
+ broken: error instanceof Error ? error.message : String(error),
146
+ }
147
+ }
148
+ })
149
+ }
150
+
151
+ function resolve(id) {
152
+ const { file } = paths(id)
153
+ try {
154
+ return { ...readManifest(file, id), path: file }
155
+ } catch (error) {
156
+ throw new Error(
157
+ `tuiPluginStore: artifact ${JSON.stringify(id)} is broken: `
158
+ + `${error instanceof Error ? error.message : String(error)}`,
159
+ )
160
+ }
161
+ }
162
+
163
+ function save(input, saveOptions = {}) {
164
+ const manifest = validateManifest({
165
+ schemaVersion: 0,
166
+ id: input?.id,
167
+ kind: input?.kind,
168
+ name: input?.name,
169
+ purpose: input?.purpose,
170
+ source: input?.source,
171
+ code: { client: input?.clientCode },
172
+ }, input?.id)
173
+ const { dir, file } = paths(manifest.id)
174
+ const replace = saveOptions.replace === true
175
+ if (existsSync(dir) && !replace) {
176
+ throw new Error(`tuiPluginStore: artifact ${JSON.stringify(manifest.id)} already exists`)
177
+ }
178
+ mkdirSync(dir, { recursive: true })
179
+ const temporary = path.join(dir, `.${MANIFEST}.${process.pid}.${Date.now()}.tmp`)
180
+ try {
181
+ writeFileSync(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' })
182
+ renameSync(temporary, file)
183
+ } finally {
184
+ rmSync(temporary, { force: true })
185
+ }
186
+ return { id: manifest.id, path: file }
187
+ }
188
+
189
+ function remove(id) {
190
+ const { dir } = paths(id)
191
+ if (!existsSync(dir)) return false
192
+ rmSync(dir, { recursive: true, force: false })
193
+ return true
194
+ }
195
+
196
+ return { root, list, resolve, save, remove }
197
+ }
package/lib/tui-theme.js CHANGED
@@ -7,8 +7,8 @@
7
7
  * flushed from `bindNotify`.
8
8
  */
9
9
 
10
- import { readFileSync } from 'node:fs'
11
- import { isAbsolute } from 'node:path'
10
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
11
+ import { dirname, isAbsolute } from 'node:path'
12
12
  import { CORDIS_METHODS } from './cordis-protocol.js'
13
13
 
14
14
  export const name = 'tui-theme'
@@ -77,6 +77,24 @@ const BACKGROUND_SOURCE_FIELDS = new Set(['kind', 'path', 'mediaType', 'base64']
77
77
  const BACKGROUND_ANCHOR_FIELDS = new Set(['x', 'y'])
78
78
  const PROTOCOL = 0
79
79
 
80
+ function readSettings(settingsPath) {
81
+ if (typeof settingsPath !== 'string' || settingsPath.length === 0) return {}
82
+ try {
83
+ const value = JSON.parse(readFileSync(settingsPath, 'utf8'))
84
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}
85
+ } catch {
86
+ return {}
87
+ }
88
+ }
89
+
90
+ function writePreferred(settingsPath, id) {
91
+ if (typeof settingsPath !== 'string' || settingsPath.length === 0) return
92
+ const settings = readSettings(settingsPath)
93
+ settings.theme = id
94
+ mkdirSync(dirname(settingsPath), { recursive: true })
95
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`)
96
+ }
97
+
80
98
  /**
81
99
  * Validate a palette against tui-palette protocol 0 (closed token names).
82
100
  * @param {unknown} palette
@@ -207,13 +225,18 @@ function validateTokenMap(map, which) {
207
225
  * `register` grows the catalog (like adding an agent preset). `/theme` and
208
226
  * `activate` switch which pack covers. Builtin `default` is never mutated.
209
227
  * @param {object} ctx
210
- * @param {{ notify?: (method: string, params: object) => void }} [options]
211
- * @returns {{ register: Function, activate: Function, list: Function, active: Function, subscribe: Function, observeSelected: Function, exportInspectTokens: Function, bindNotify: Function, flush: Function }}
228
+ * @param {{ notify?: (method: string, params: object) => void, settingsPath?: string }} [options]
229
+ * @returns {{ register: Function, activate: Function, list: Function, active: Function, preferred: Function, subscribe: Function, observeSelected: Function, exportInspectTokens: Function, bindNotify: Function, flush: Function }}
212
230
  */
213
231
  export function installTuiTheme(ctx, options = {}) {
214
232
  const palettes = new Map()
215
233
  const queue = []
216
234
  const listeners = new Set()
235
+ const settingsPath = options.settingsPath
236
+ const savedTheme = readSettings(settingsPath).theme
237
+ let preferredId = typeof savedTheme === 'string' && savedTheme.length > 0
238
+ ? savedTheme
239
+ : 'default'
217
240
  let send = typeof options.notify === 'function' ? options.notify : undefined
218
241
  let activeId = 'default'
219
242
  let activationRevision = 0
@@ -271,6 +294,8 @@ export function installTuiTheme(ctx, options = {}) {
271
294
 
272
295
  function activate(id) {
273
296
  select(id, true)
297
+ preferredId = id
298
+ writePreferred(settingsPath, id)
274
299
  }
275
300
 
276
301
  function observeSelected(params) {
@@ -279,6 +304,8 @@ export function installTuiTheme(ctx, options = {}) {
279
304
  }
280
305
  if (params.protocol !== PROTOCOL) return false
281
306
  select(params.id, false)
307
+ preferredId = params.id
308
+ writePreferred(settingsPath, params.id)
282
309
  return true
283
310
  }
284
311
 
@@ -319,7 +346,7 @@ export function installTuiTheme(ctx, options = {}) {
319
346
  ...(owner === undefined ? {} : { owner: { pluginId: owner } }),
320
347
  })
321
348
  if (cover) {
322
- activate(validated.id)
349
+ select(validated.id, true)
323
350
  leaseRevision = activationRevision
324
351
  }
325
352
  return () => {
@@ -331,9 +358,9 @@ export function installTuiTheme(ctx, options = {}) {
331
358
  const restoreId = previousId !== undefined && palettes.get(previousId)?.loaded === true
332
359
  ? previousId
333
360
  : 'default'
334
- activate(restoreId)
361
+ select(restoreId, true)
335
362
  } else if (wasActive) {
336
- activate('default')
363
+ select('default', true)
337
364
  }
338
365
  if (owner === undefined) {
339
366
  palettes.delete(validated.id)
@@ -403,6 +430,10 @@ export function installTuiTheme(ctx, options = {}) {
403
430
  return activeId
404
431
  }
405
432
 
433
+ function preferred() {
434
+ return preferredId
435
+ }
436
+
406
437
  function owner(id) {
407
438
  return palettes.get(id)?.owner
408
439
  }
@@ -440,6 +471,7 @@ export function installTuiTheme(ctx, options = {}) {
440
471
  activate,
441
472
  list,
442
473
  active,
474
+ preferred,
443
475
  owner,
444
476
  isLoaded,
445
477
  subscribe,
@@ -458,6 +490,6 @@ export function installTuiTheme(ctx, options = {}) {
458
490
  }
459
491
 
460
492
  /** Cordis plugin entry: provide the client-tree theme registry. */
461
- export function apply(ctx) {
462
- installTuiTheme(ctx)
493
+ export function apply(ctx, options) {
494
+ installTuiTheme(ctx, options)
463
495
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "martty",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Terminal-native ACP client UI; Cordis client tree, any ACP agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "main": "lib/index.js",
16
16
  "scripts": {
17
17
  "pretest": "node --test ../scripts/workflow-release.test.mjs ../scripts/package-alias.test.mjs",
18
- "test": "node --test ../scripts/package-native.test.mjs ../scripts/check-release-tag.test.mjs ../scripts/check-static-elf.test.mjs ../scripts/smoke-old-linux.test.mjs ../scripts/cargo-guard.test.mjs ../scripts/build-npm.test.mjs ../scripts/client-profile.test.mjs ../scripts/plugin-runner.test.mjs ../scripts/profile-link-resolution.test.mjs ../scripts/release.test.mjs ../scripts/jsonrpc-line-transport.test.mjs ../scripts/tui-theme.test.mjs ../scripts/tui-presets.test.mjs ../scripts/tui-slots.test.mjs ../scripts/tui-commands.test.mjs ../scripts/tui-overlay.test.mjs ../scripts/mux.test.mjs ../scripts/acp-client.test.mjs ../scripts/acp-client-events.test.mjs ../scripts/acp-session-config.test.mjs ../scripts/acp-session-plan.test.mjs ../scripts/acp-session-stats.test.mjs ../scripts/acp-session-status.test.mjs ../scripts/plan-view.test.mjs ../scripts/stats-view.test.mjs ../scripts/status-view.test.mjs ../scripts/deepseek-logo.test.mjs ../scripts/runner.test.mjs ../scripts/inspect.test.mjs ../scripts/creator-overlay.test.mjs ../scripts/real-agent-e2e.test.mjs"
18
+ "test": "node --test ../scripts/package-native.test.mjs ../scripts/check-release-tag.test.mjs ../scripts/check-static-elf.test.mjs ../scripts/smoke-old-linux.test.mjs ../scripts/cargo-guard.test.mjs ../scripts/build-npm.test.mjs ../scripts/client-profile.test.mjs ../scripts/plugin-runner.test.mjs ../scripts/profile-link-resolution.test.mjs ../scripts/release.test.mjs ../scripts/jsonrpc-line-transport.test.mjs ../scripts/tui-theme.test.mjs ../scripts/tui-presets.test.mjs ../scripts/tui-plugin-store.test.mjs ../scripts/tui-local-plugins.test.mjs ../scripts/tui-client-plugin-registry.test.mjs ../scripts/tui-slots.test.mjs ../scripts/tui-commands.test.mjs ../scripts/tui-overlay.test.mjs ../scripts/mux.test.mjs ../scripts/acp-client.test.mjs ../scripts/acp-client-events.test.mjs ../scripts/acp-session-config.test.mjs ../scripts/acp-session-plan.test.mjs ../scripts/acp-session-stats.test.mjs ../scripts/acp-session-status.test.mjs ../scripts/plan-view.test.mjs ../scripts/stats-view.test.mjs ../scripts/status-view.test.mjs ../scripts/deepseek-logo.test.mjs ../scripts/runner.test.mjs ../scripts/inspect.test.mjs ../scripts/creator-overlay.test.mjs ../scripts/real-agent-e2e.test.mjs"
19
19
  },
20
20
  "publishConfig": {
21
21
  "access": "public",
@@ -43,6 +43,7 @@
43
43
  "./profile-acp-client": "./lib/profile-acp-client.js",
44
44
  "./acp-host": "./lib/acp-host.js",
45
45
  "./creator-overlay": "./lib/creator-overlay.js",
46
+ "./client-plugin-registry": "./lib/tui-client-plugin-registry.js",
46
47
  "./cordis-client-runner": "./lib/inspect.js",
47
48
  "./runner": "./lib/runner.js",
48
49
  "./cordis.patch.yml": "./cordis.patch.yml",
@@ -69,7 +70,7 @@
69
70
  },
70
71
  "dependencies": {
71
72
  "@deepseek-ai/cordis": "^4.0.1",
72
- "@openma/deepseek-harness-acp": "0.4.13"
73
+ "@openma/deepseek-harness-acp": "0.4.19"
73
74
  },
74
75
  "devDependencies": {
75
76
  "@deepseek-ai/dsh": "0.1.0-rc.6"