martty 0.2.33 → 0.2.35
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/README.md +108 -11
- package/bin/martty.js +52 -5
- package/lib/acp-client.js +296 -20
- package/lib/acp-registry.snapshot.json +1450 -0
- package/lib/acp-session-config.js +23 -0
- package/lib/acp-session-plan.js +14 -2
- package/lib/acp-session-stats.js +22 -5
- package/lib/acp-session-status.js +70 -25
- package/lib/agent.js +24 -4
- package/lib/boot.js +29 -5
- package/lib/command-args.js +28 -0
- package/lib/download.js +165 -0
- package/lib/harness-discovery.js +41 -0
- package/lib/harness-package.js +200 -0
- package/lib/harness-registry.js +468 -0
- package/lib/harness-removal.js +88 -0
- package/lib/harness-view.js +639 -46
- package/lib/harnesses.js +1053 -41
- package/lib/index.js +4 -0
- package/lib/mux.js +23 -0
- package/lib/plan-view.js +18 -3
- package/lib/status-view.js +2 -0
- package/lib/tui-commands.js +7 -1
- package/lib/tui-overlay.js +27 -5
- package/package.json +7 -4
- 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/index.js
CHANGED
|
@@ -223,8 +223,10 @@ export async function applyShell(ctx, options = {}) {
|
|
|
223
223
|
},
|
|
224
224
|
onAcp(direction, message) {
|
|
225
225
|
if (direction === 'client') {
|
|
226
|
+
agent.observeClient?.(message)
|
|
226
227
|
clientEvents.observeClient(message)
|
|
227
228
|
} else {
|
|
229
|
+
agent.observeAgent?.(message)
|
|
228
230
|
clientEvents.observeAgent(message)
|
|
229
231
|
}
|
|
230
232
|
},
|
|
@@ -260,6 +262,8 @@ export async function applyShell(ctx, options = {}) {
|
|
|
260
262
|
throw new Error(`unsupported Cordis TUI method: ${String(message.method)}`)
|
|
261
263
|
},
|
|
262
264
|
})
|
|
265
|
+
agent.onSwitch?.(() => mux.resetAgent())
|
|
266
|
+
agent.onFailure?.((error) => mux.failAgent(error))
|
|
263
267
|
const notifyTui = (method, params) => mux.notifyTui(method, params)
|
|
264
268
|
republishCompositorState = () => {
|
|
265
269
|
handle.bindNotify(notifyTui)
|
package/lib/mux.js
CHANGED
|
@@ -126,6 +126,8 @@ export function onJsonLines(source, onLine) {
|
|
|
126
126
|
* notifyTui: (method: string, params?: object) => void,
|
|
127
127
|
* requestAgent: (method: string, params?: object) => Promise<unknown>,
|
|
128
128
|
* requestTui: (method: string, params?: object) => Promise<unknown>,
|
|
129
|
+
* resetAgent: () => void,
|
|
130
|
+
* failAgent: (error: Error) => void,
|
|
129
131
|
* }}
|
|
130
132
|
*/
|
|
131
133
|
export function muxAcpAndCompositor(opts) {
|
|
@@ -137,6 +139,7 @@ export function muxAcpAndCompositor(opts) {
|
|
|
137
139
|
/** @type {Map<string, { resolve: (value: unknown) => void, reject: (error: Error) => void }>} */
|
|
138
140
|
const pendingAgent = new Map()
|
|
139
141
|
const pendingTui = new Map()
|
|
142
|
+
const pendingClient = new Set()
|
|
140
143
|
let nextHostId = 0
|
|
141
144
|
let nextTuiId = 0
|
|
142
145
|
/** @type {unknown} */
|
|
@@ -167,6 +170,7 @@ export function muxAcpAndCompositor(opts) {
|
|
|
167
170
|
if (agentCordis !== null) onHost?.(message)
|
|
168
171
|
return
|
|
169
172
|
}
|
|
173
|
+
if (message?.id !== undefined && typeof message.method !== 'string') pendingClient.delete(message.id)
|
|
170
174
|
onAcp?.('agent', message)
|
|
171
175
|
tui.output.write(`${line}\n`)
|
|
172
176
|
if (initializeId !== undefined && message && typeof message === 'object' && message.id === initializeId) {
|
|
@@ -244,10 +248,29 @@ export function muxAcpAndCompositor(opts) {
|
|
|
244
248
|
initializeId = outgoing.id
|
|
245
249
|
}
|
|
246
250
|
onAcp?.('client', outgoing)
|
|
251
|
+
if (outgoing.id !== undefined && typeof outgoing.method === 'string') pendingClient.add(outgoing.id)
|
|
247
252
|
writeJsonLine(agent.stdin, outgoing)
|
|
248
253
|
})
|
|
249
254
|
|
|
255
|
+
const failAgent = (error) => {
|
|
256
|
+
for (const waiter of pendingAgent.values()) waiter.reject(error)
|
|
257
|
+
pendingAgent.clear()
|
|
258
|
+
const requests = [...pendingClient]
|
|
259
|
+
pendingClient.clear()
|
|
260
|
+
for (const id of requests) {
|
|
261
|
+
const response = { jsonrpc: '2.0', id, error: { code: -32603, message: error.message } }
|
|
262
|
+
onAcp?.('agent', response)
|
|
263
|
+
writeJsonLine(tui.output, response)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
250
267
|
return {
|
|
268
|
+
failAgent,
|
|
269
|
+
resetAgent() {
|
|
270
|
+
initializeId = undefined
|
|
271
|
+
agentCordis = null
|
|
272
|
+
failAgent(new Error('ACP Agent was replaced'))
|
|
273
|
+
},
|
|
251
274
|
notifyTui(method, params) {
|
|
252
275
|
writeJsonLine(
|
|
253
276
|
tui.output,
|
package/lib/plan-view.js
CHANGED
|
@@ -43,7 +43,7 @@ function dockNodes(plan) {
|
|
|
43
43
|
const total = plan.entries.length
|
|
44
44
|
if (total === 0) return []
|
|
45
45
|
const completed = plan.entries.filter((entry) => entry.status === 'completed').length
|
|
46
|
-
const focus = plan.entries.find((entry) => entry.status
|
|
46
|
+
const focus = plan.entries.find((entry) => isInProgress(entry.status))
|
|
47
47
|
?? plan.entries.find((entry) => entry.status !== 'completed')
|
|
48
48
|
?? plan.entries.at(-1)
|
|
49
49
|
const action = { kind: 'command', name: 'plan-view', args: '' }
|
|
@@ -53,7 +53,10 @@ function dockNodes(plan) {
|
|
|
53
53
|
tone: 'caption', status: completed === total ? 'done' : 'running', action,
|
|
54
54
|
},
|
|
55
55
|
...(focus ? [{
|
|
56
|
-
id: 'focus', kind: 'generic', title: focus.content,
|
|
56
|
+
id: 'focus', kind: 'generic', title: focus.content,
|
|
57
|
+
body: statusLabel(focus.status),
|
|
58
|
+
...(isInProgress(focus.status) ? { status: 'running' } : {}),
|
|
59
|
+
tone: 'caption', action,
|
|
57
60
|
}] : []),
|
|
58
61
|
]
|
|
59
62
|
}
|
|
@@ -100,10 +103,22 @@ function viewNodes(plan) {
|
|
|
100
103
|
} else if (entry.status === 'cancelled' || entry.status === 'failed') {
|
|
101
104
|
item = `- [ ] ~~${content}~~`
|
|
102
105
|
} else {
|
|
103
|
-
item = entry.status
|
|
106
|
+
item = isInProgress(entry.status) ? `- ◐ **${content}** · in progress` : `- [ ] ${content}`
|
|
104
107
|
}
|
|
105
108
|
if (entry.priority) item += ` · priority · ${entry.priority}`
|
|
106
109
|
lines.push(item)
|
|
107
110
|
}
|
|
108
111
|
return [{ id: 'content', kind: 'markdown', text: lines.join('\n') }]
|
|
109
112
|
}
|
|
113
|
+
|
|
114
|
+
function isInProgress(status) {
|
|
115
|
+
return status === 'in_progress' || status === 'in-progress'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function statusLabel(status) {
|
|
119
|
+
if (isInProgress(status)) return 'in progress'
|
|
120
|
+
if (status === 'completed') return 'completed'
|
|
121
|
+
if (status === 'cancelled') return 'cancelled'
|
|
122
|
+
if (status === 'failed') return 'failed'
|
|
123
|
+
return 'pending'
|
|
124
|
+
}
|
package/lib/status-view.js
CHANGED
|
@@ -41,8 +41,10 @@ function statusMarkdown(status, stats) {
|
|
|
41
41
|
const lines = []
|
|
42
42
|
lines.push(`- state · ${status.state ?? 'idle'}`)
|
|
43
43
|
lines.push(`- acp · ${status.connection ?? 'not attached'}`)
|
|
44
|
+
if (status.error) lines.push(`- connection error · ${status.error}`)
|
|
44
45
|
if (status.auth?.status !== undefined) {
|
|
45
46
|
lines.push(`- auth · ${status.auth.status}${status.auth.method ? ` · ${status.auth.method}` : ''}`)
|
|
47
|
+
if (status.auth.message) lines.push(`- auth error · ${status.auth.message}`)
|
|
46
48
|
}
|
|
47
49
|
lines.push(`- session · ${status.session?.bound ? status.session.sessionId : 'unbound'}`)
|
|
48
50
|
if (status.server !== undefined) lines.push(`- server · ${status.server}`)
|
package/lib/tui-commands.js
CHANGED
|
@@ -75,7 +75,7 @@ function validateInput(input) {
|
|
|
75
75
|
throw new Error('tuiCommands.register: each input option must be an object')
|
|
76
76
|
}
|
|
77
77
|
const unknown = Object.keys(option)
|
|
78
|
-
.filter((key) => !['value', 'label', 'description'].includes(key))
|
|
78
|
+
.filter((key) => !['value', 'label', 'description', 'disabled'].includes(key))
|
|
79
79
|
if (unknown.length > 0) {
|
|
80
80
|
throw new Error(`tuiCommands.register: unknown input option field(s) ${unknown.join(', ')}`)
|
|
81
81
|
}
|
|
@@ -90,6 +90,7 @@ function validateInput(input) {
|
|
|
90
90
|
}
|
|
91
91
|
return {
|
|
92
92
|
value: option.value,
|
|
93
|
+
...(option.disabled === undefined ? {} : { disabled: booleanOption(option.disabled) }),
|
|
93
94
|
...(option.label === undefined ? {} : { label: option.label }),
|
|
94
95
|
...(option.description === undefined ? {} : { description: option.description }),
|
|
95
96
|
}
|
|
@@ -98,6 +99,11 @@ function validateInput(input) {
|
|
|
98
99
|
return normalized
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
function booleanOption(value) {
|
|
103
|
+
if (typeof value !== 'boolean') throw new Error('tuiCommands.register: input option disabled must be a boolean')
|
|
104
|
+
return value
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
/**
|
|
102
108
|
* @param {object} ctx
|
|
103
109
|
* @param {{ notify?: (method: string, params: object) => void }} [options]
|
package/lib/tui-overlay.js
CHANGED
|
@@ -151,17 +151,31 @@ function validateSelect(input) {
|
|
|
151
151
|
if (option.description !== undefined && typeof option.description !== 'string') {
|
|
152
152
|
throw new Error(`tuiOverlay.openSelect: options[${index}].description must be a string`)
|
|
153
153
|
}
|
|
154
|
+
if (option.disabled !== undefined && typeof option.disabled !== 'boolean') {
|
|
155
|
+
throw new Error(`tuiOverlay.openSelect: options[${index}].disabled must be a boolean`)
|
|
156
|
+
}
|
|
157
|
+
if (option.deletable !== undefined && typeof option.deletable !== 'boolean') {
|
|
158
|
+
throw new Error(`tuiOverlay.openSelect: options[${index}].deletable must be a boolean`)
|
|
159
|
+
}
|
|
160
|
+
if (option.group !== undefined && (typeof option.group !== 'string'
|
|
161
|
+
|| option.group.trim().length === 0 || /[\x00-\x1f\x7f-\x9f\u2028\u2029]/.test(option.group))) {
|
|
162
|
+
throw new Error(`tuiOverlay.openSelect: options[${index}].group must be a non-empty single-line string`)
|
|
163
|
+
}
|
|
154
164
|
return {
|
|
155
165
|
value: option.value,
|
|
156
166
|
label: option.label,
|
|
167
|
+
...(option.disabled === undefined ? {} : { disabled: option.disabled }),
|
|
168
|
+
...(option.deletable === undefined ? {} : { deletable: option.deletable }),
|
|
157
169
|
...(option.description === undefined ? {} : { description: option.description }),
|
|
170
|
+
...(option.group === undefined ? {} : { group: option.group }),
|
|
158
171
|
}
|
|
159
172
|
})
|
|
160
173
|
const value = input.value === undefined ? options[0].value : input.value
|
|
161
174
|
if (typeof value !== 'string' || !values.has(value)) {
|
|
162
175
|
throw new Error('tuiOverlay.openSelect: value must match one option')
|
|
163
176
|
}
|
|
164
|
-
return { kind: 'select', id: input.id, title: input.title, value, options
|
|
177
|
+
return { kind: 'select', id: input.id, title: input.title, value, options,
|
|
178
|
+
...(input.searchable === true ? { searchable: true } : {}) }
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
/**
|
|
@@ -213,10 +227,15 @@ export function installTuiOverlay(ctx, options = {}) {
|
|
|
213
227
|
}
|
|
214
228
|
|
|
215
229
|
function openSelect(options, handlers = {}) {
|
|
230
|
+
const select = validateSelect(options)
|
|
216
231
|
if (current !== null) {
|
|
217
|
-
|
|
232
|
+
if (current.overlay.kind !== 'select' || current.overlay.id !== select.id) {
|
|
233
|
+
throw new Error(`tuiOverlay.openSelect: overlay "${current.overlay.id}" is already open`)
|
|
234
|
+
}
|
|
235
|
+
// A background catalog refresh replaces only this modal's snapshot.
|
|
236
|
+
// Retire its old handles so an earlier async callback cannot close it.
|
|
237
|
+
current.closed = true
|
|
218
238
|
}
|
|
219
|
-
const select = validateSelect(options)
|
|
220
239
|
const entry = { overlay: select, handlers, closed: false }
|
|
221
240
|
current = entry
|
|
222
241
|
publish(structuredClone(select))
|
|
@@ -265,7 +284,7 @@ export function installTuiOverlay(ctx, options = {}) {
|
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
if (entry.overlay.kind === 'select') {
|
|
268
|
-
if (!['change', 'submit', 'cancel'].includes(params.event)) {
|
|
287
|
+
if (!['change', 'submit', 'cancel', 'delete'].includes(params.event)) {
|
|
269
288
|
throw new Error(`tuiOverlay.dispatch: unknown select event "${String(params.event)}"`)
|
|
270
289
|
}
|
|
271
290
|
if (params.event !== 'cancel') {
|
|
@@ -273,6 +292,9 @@ export function installTuiOverlay(ctx, options = {}) {
|
|
|
273
292
|
|| !entry.overlay.options.some((option) => option.value === params.value)) {
|
|
274
293
|
throw new Error('tuiOverlay.dispatch: select value is not an option')
|
|
275
294
|
}
|
|
295
|
+
if (entry.overlay.options.find((option) => option.value === params.value)?.disabled) return
|
|
296
|
+
if (params.event === 'delete' && (!entry.overlay.options.find(option => option.value === params.value)?.deletable
|
|
297
|
+
|| typeof entry.handlers.onDelete !== 'function')) return
|
|
276
298
|
entry.overlay.value = params.value
|
|
277
299
|
}
|
|
278
300
|
if (params.event === 'change') {
|
|
@@ -280,7 +302,7 @@ export function installTuiOverlay(ctx, options = {}) {
|
|
|
280
302
|
}
|
|
281
303
|
const handler = params.event === 'submit'
|
|
282
304
|
? entry.handlers.onSubmit
|
|
283
|
-
: entry.handlers.onCancel
|
|
305
|
+
: params.event === 'delete' ? entry.handlers.onDelete : entry.handlers.onCancel
|
|
284
306
|
entry.closed = true
|
|
285
307
|
if (current === entry) {
|
|
286
308
|
current = null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "martty",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.35",
|
|
4
4
|
"description": "Terminal-native ACP client UI; Cordis client tree, any ACP agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "lib/index.js",
|
|
16
16
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"test:harness-ui": "node --test ../scripts/harness-management-tui.test.mjs",
|
|
18
|
+
"pretest": "node --test ../scripts/workflow-release.test.mjs ../scripts/package-alias.test.mjs ../scripts/harness-removal.test.mjs",
|
|
19
|
+
"test": "node --test ../scripts/download.test.mjs ../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/packaged-acp-permission.test.mjs ../scripts/profile-smoke-tui.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/tui-agents.test.mjs ../scripts/tui-queue.test.mjs ../scripts/mux.test.mjs ../scripts/acp-client.test.mjs ../scripts/harnesses.test.mjs ../scripts/harness-discovery.test.mjs ../scripts/harness-discovery-scenario.test.mjs ../scripts/harness-view.test.mjs ../scripts/harness-onboarding.test.mjs ../scripts/harness-registry.test.mjs ../scripts/harness-package.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/agents-view.test.mjs ../scripts/plan-view.test.mjs ../scripts/queue-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
20
|
"test:profile-install-matrix": "node --test ../scripts/profile-install-matrix.test.mjs"
|
|
20
21
|
},
|
|
21
22
|
"publishConfig": {
|
|
@@ -75,7 +76,9 @@
|
|
|
75
76
|
},
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
78
|
-
"@openma/deepseek-harness-acp": "0.4.
|
|
79
|
+
"@openma/deepseek-harness-acp": "0.4.29",
|
|
80
|
+
"cross-spawn": "^7.0.6",
|
|
81
|
+
"node-downloader-helper": "2.1.11"
|
|
79
82
|
},
|
|
80
83
|
"devDependencies": {
|
|
81
84
|
"@deepseek-ai/dsh": "0.1.1-rc.2"
|
|
Binary file
|
package/vendor/darwin-x64/martty
CHANGED
|
Binary file
|
|
Binary file
|
package/vendor/linux-x64/martty
CHANGED
|
Binary file
|
|
Binary file
|