martty 0.2.36 → 0.2.37
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/lib/acp-host.js +91 -0
- package/lib/creator-overlay.js +6 -0
- package/lib/mux.js +21 -2
- package/lib/tui-presets.js +8 -2
- package/package.json +1 -1
- package/vendor/darwin-arm64/martty +0 -0
- package/vendor/darwin-x64/martty +0 -0
- package/vendor/linux-arm64/martty +0 -0
- package/vendor/linux-x64/martty +0 -0
- package/vendor/win32-x64/martty.exe +0 -0
package/lib/acp-host.js
CHANGED
|
@@ -115,10 +115,101 @@ export function installPermissionPresetsCompatibility(permissionPresets) {
|
|
|
115
115
|
return true
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
function snapshotRowHeader(row) {
|
|
119
|
+
if (row === null || typeof row !== 'object') return undefined
|
|
120
|
+
const header = row.header
|
|
121
|
+
if (header === null || typeof header !== 'object' || typeof header.id !== 'string') {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
return header
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One legacy `list()` row: the header itself, plus the snapshot fields a newer
|
|
129
|
+
* Host consumer reads off the same row (`header`, `revision`, sizes).
|
|
130
|
+
*/
|
|
131
|
+
function legacyListRow(snapshot) {
|
|
132
|
+
const header = snapshotRowHeader(snapshot)
|
|
133
|
+
if (header === undefined) return undefined
|
|
134
|
+
const row = { ...header }
|
|
135
|
+
for (const key of ['header', 'revision', 'eventCount', 'sizeBytes']) {
|
|
136
|
+
if (snapshot[key] === undefined) continue
|
|
137
|
+
Object.defineProperty(row, key, {
|
|
138
|
+
configurable: true,
|
|
139
|
+
value: key === 'header' ? header : snapshot[key],
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
return row
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* dsh 0.1.5 folded `listSnapshots()` into `list()` and replaced `inspect()`
|
|
147
|
+
* with per-session handles: `list()` now resolves to `{ header, revision }`
|
|
148
|
+
* snapshots, and one stored log is read through `open(id, 'read')`. ACP 0.4.x
|
|
149
|
+
* still filters listed rows by the row's own `cwd` and replays titles and
|
|
150
|
+
* resume history through `inspect()`, so project the removed header/inspection
|
|
151
|
+
* surface back onto the live service until ACP can require the handle API.
|
|
152
|
+
*
|
|
153
|
+
* Rows stay snapshot-compatible, so a newer Host consumer reading `.header`
|
|
154
|
+
* keeps working while ACP reads the header fields directly.
|
|
155
|
+
*/
|
|
156
|
+
export function installSessionPersistenceCompatibility(ctx) {
|
|
157
|
+
const service = ctx?.get?.('sessionPersistence')
|
|
158
|
+
if (service === undefined || service === null) return false
|
|
159
|
+
const list = service.list
|
|
160
|
+
if (typeof list !== 'function' || typeof service.open !== 'function') return false
|
|
161
|
+
if (list.dshTuiReturnsHeaders === true) return false
|
|
162
|
+
|
|
163
|
+
const compatibleList = async function (options) {
|
|
164
|
+
const listed = await list.call(this, options)
|
|
165
|
+
if (!Array.isArray(listed)) return listed
|
|
166
|
+
return listed.map((row) => legacyListRow(row) ?? row)
|
|
167
|
+
}
|
|
168
|
+
Object.defineProperty(compatibleList, 'dshTuiReturnsHeaders', { value: true })
|
|
169
|
+
|
|
170
|
+
// The class methods live on the prototype: restore by deleting the own
|
|
171
|
+
// property when the patch shadowed one instead of replacing an own value.
|
|
172
|
+
const listDescriptor = Object.getOwnPropertyDescriptor(service, 'list')
|
|
173
|
+
Object.defineProperty(service, 'list', { configurable: true, value: compatibleList })
|
|
174
|
+
|
|
175
|
+
let compatibleInspect
|
|
176
|
+
if (typeof service.inspect !== 'function') {
|
|
177
|
+
compatibleInspect = async function (id, signal) {
|
|
178
|
+
const options = signal === undefined ? undefined : { signal }
|
|
179
|
+
const handle = await service.open(id, 'read', options)
|
|
180
|
+
try {
|
|
181
|
+
const { events } = await handle.read(0, undefined, options)
|
|
182
|
+
const header = handle.header
|
|
183
|
+
// `meta` is the old coordinator's field; `header` is what ACP reads.
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
meta: header,
|
|
186
|
+
header,
|
|
187
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
188
|
+
events,
|
|
189
|
+
})
|
|
190
|
+
} finally {
|
|
191
|
+
await handle.close().catch(() => {})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
Object.defineProperty(service, 'inspect', { configurable: true, value: compatibleInspect })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ctx.effect?.(() => () => {
|
|
198
|
+
if (service.list !== compatibleList) return
|
|
199
|
+
if (listDescriptor === undefined) delete service.list
|
|
200
|
+
else Object.defineProperty(service, 'list', listDescriptor)
|
|
201
|
+
if (service.inspect === compatibleInspect) delete service.inspect
|
|
202
|
+
}, 'dsh-tui.session-persistence-compatibility')
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
118
206
|
async function mountHostCompatibility(ctx) {
|
|
119
207
|
// The service can come from the active Host even when module resolution
|
|
120
208
|
// below lands on ACP's older peer copy, so adapt the live instance directly.
|
|
121
209
|
installPermissionPresetsCompatibility(ctx.permissionPresets ?? ctx.get?.('permissionPresets'))
|
|
210
|
+
// Not injected here on purpose: the profile's base bundle owns persistence,
|
|
211
|
+
// and `ctx.get` reads the live service without adding a mount dependency.
|
|
212
|
+
installSessionPersistenceCompatibility(ctx)
|
|
122
213
|
|
|
123
214
|
const sessionModule = resolvedHostModule(ctx, '@deepseek-ai/dsh-session')
|
|
124
215
|
if (sessionModule !== undefined) {
|
package/lib/creator-overlay.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { installSessionPersistenceCompatibility } from './acp-host.js'
|
|
7
8
|
import { createTuiPluginStore } from './tui-plugin-store.js'
|
|
8
9
|
|
|
9
10
|
export const name = 'tui-creator-overlay'
|
|
@@ -192,6 +193,11 @@ const skillContent = skillDocument.slice(skillFrontmatter[0].length)
|
|
|
192
193
|
* @param {{ preset?: string }} [config]
|
|
193
194
|
*/
|
|
194
195
|
export async function apply(ctx, config = {}) {
|
|
196
|
+
// Standalone launches mount this overlay directly into the ACP Host tree,
|
|
197
|
+
// which never loads martty/acp-host; both trees need the ACP compatibility
|
|
198
|
+
// surface, and the installer is idempotent when the profile path already
|
|
199
|
+
// applied it on this service instance.
|
|
200
|
+
installSessionPersistenceCompatibility(ctx)
|
|
195
201
|
const preset = config.preset ?? 'cordis'
|
|
196
202
|
if (typeof preset !== 'string' || preset.length === 0) {
|
|
197
203
|
throw new Error('tui-creator-overlay: preset must be a non-empty string')
|
package/lib/mux.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* off the painter.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { StringDecoder } from 'node:string_decoder'
|
|
12
13
|
import { CORDIS_CAPABILITY, CORDIS_METHODS, readCordisCapability } from './cordis-protocol.js'
|
|
13
14
|
|
|
14
15
|
const COMPOSITOR_METHODS = Object.freeze(new Set([
|
|
@@ -95,13 +96,20 @@ export function writeJsonLine(dest, value) {
|
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* Pipe NDJSON, invoking `onLine` for each complete line (trimmed, non-empty).
|
|
99
|
+
*
|
|
100
|
+
* Frames are UTF-8: decoding each chunk with `chunk.toString('utf8')` would
|
|
101
|
+
* turn any multi-byte character split across a chunk boundary into U+FFFD
|
|
102
|
+
* replacement characters (silent corruption in both directions). A
|
|
103
|
+
* `StringDecoder` carries the incomplete tail across chunks, and `end()`
|
|
104
|
+
* flushes a final frame that arrived without a trailing newline.
|
|
105
|
+
*
|
|
98
106
|
* @param {import('node:stream').Readable} source
|
|
99
107
|
* @param {(line: string) => void} onLine
|
|
100
108
|
*/
|
|
101
109
|
export function onJsonLines(source, onLine) {
|
|
102
110
|
let buffer = ''
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
const decoder = new StringDecoder('utf8')
|
|
112
|
+
const drain = () => {
|
|
105
113
|
for (;;) {
|
|
106
114
|
const newline = buffer.indexOf('\n')
|
|
107
115
|
if (newline < 0) break
|
|
@@ -110,6 +118,17 @@ export function onJsonLines(source, onLine) {
|
|
|
110
118
|
if (line.length === 0) continue
|
|
111
119
|
onLine(line)
|
|
112
120
|
}
|
|
121
|
+
}
|
|
122
|
+
source.on('data', (chunk) => {
|
|
123
|
+
buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk)
|
|
124
|
+
drain()
|
|
125
|
+
})
|
|
126
|
+
source.on('end', () => {
|
|
127
|
+
buffer += decoder.end()
|
|
128
|
+
drain()
|
|
129
|
+
const line = buffer.trim()
|
|
130
|
+
buffer = ''
|
|
131
|
+
if (line.length > 0) onLine(line)
|
|
113
132
|
})
|
|
114
133
|
}
|
|
115
134
|
|
package/lib/tui-presets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** UI Presets compose several UI plugin contributions into one saved choice. */
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { Service } from '@deepseek-ai/cordis'
|
|
6
6
|
import { CORDIS_METHODS } from './cordis-protocol.js'
|
|
@@ -72,7 +72,13 @@ function writePreferred(settingsPath, id) {
|
|
|
72
72
|
const settings = readSettings(settingsPath)
|
|
73
73
|
settings.uiPreset = id
|
|
74
74
|
mkdirSync(path.dirname(settingsPath), { recursive: true })
|
|
75
|
-
|
|
75
|
+
// Atomic write (temp + rename), matching tui-theme.js: a crash mid-write
|
|
76
|
+
// must never leave a truncated settings.json behind — every later launch
|
|
77
|
+
// would otherwise start from an unreadable file that also carries the
|
|
78
|
+
// painter's language/themeMode keys.
|
|
79
|
+
const temporary = `${settingsPath}.${process.pid}.${Date.now()}.tmp`
|
|
80
|
+
writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`)
|
|
81
|
+
renameSync(temporary, settingsPath)
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
function releaseOf(value) {
|
package/package.json
CHANGED
|
Binary file
|
package/vendor/darwin-x64/martty
CHANGED
|
Binary file
|
|
Binary file
|
package/vendor/linux-x64/martty
CHANGED
|
Binary file
|
|
Binary file
|