martty 0.3.2-beta.3 → 0.3.2

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 (2) hide show
  1. package/lib/harnesses.js +55 -31
  2. package/package.json +8 -7
package/lib/harnesses.js CHANGED
@@ -16,15 +16,18 @@ import {
16
16
  installRegistryBinary,
17
17
  managedBinaryPath,
18
18
  normalizeAcpRegistry,
19
- OPENMA_PI_ACP,
20
19
  readAcpRegistrySnapshot,
21
20
  } from './harness-registry.js'
21
+ import {
22
+ migrateLegacyAcpAgents,
23
+ readCustomAcpAgents,
24
+ removeCustomAcpAgent,
25
+ upsertCustomAcpAgent,
26
+ } from '@openma/common/acp-harnesses/custom-agents'
22
27
 
23
28
  export { fetchAcpRegistry } from './harness-registry.js'
24
29
 
25
30
  const HARNESS_ID = /^[a-z0-9][a-z0-9-]*$/
26
- const PI_ACP_LATEST = OPENMA_PI_ACP
27
- const PI_ACP_SPEC = /^(?:@openma\/)?pi-acp(?:@.+)?$/
28
31
  const PI_ACP_ID = 'pi-acp'
29
32
  const PI_ACP_LEGACY_LABEL = 'Pi (OpenMA)'
30
33
  const ANSI = Object.freeze({ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', cyan: '\x1b[36m', green: '\x1b[32m' })
@@ -368,12 +371,7 @@ function validateHarness(value) {
368
371
  }
369
372
  const isNpx = /^npx(?:\.(?:cmd|bat|exe))?$/i.test(path.win32.basename(value.command))
370
373
  const normalizedArgs = isNpx
371
- ? [...args]
372
- .filter((arg) => arg !== '--yes' && arg !== '--prefer-offline')
373
- // A pinned pi-acp spec (any old `pi-acp@x` or `@openma/pi-acp@x` entry)
374
- // always resolves `latest` at runtime instead — harness upgrades ship
375
- // through npm, never through a Martty release.
376
- .map((arg) => PI_ACP_SPEC.test(arg) ? PI_ACP_LATEST : arg)
374
+ ? [...args].filter((arg) => arg !== '--yes' && arg !== '--prefer-offline')
377
375
  : [...args]
378
376
  const label = typeof value.label === 'string' && value.label.trim().length > 0
379
377
  ? value.label
@@ -389,9 +387,34 @@ function validateHarness(value) {
389
387
  }
390
388
  }
391
389
 
392
- function configuredHarnesses(settings) {
393
- if (!Array.isArray(settings.harnesses)) return []
394
- return settings.harnesses.map(validateHarness)
390
+ // Harness definitions live in <oma>/agents.json, the shared store from
391
+ // @openma/common — Backchat reads and writes the same file. Legacy rows
392
+ // still inside settings.json (and Backchat's config.toml overrides) keep
393
+ // reading through the shared module; the first shared write migrates
394
+ // harnesses[] into config.toml and strips the key so removals stick.
395
+ function settingsHome(settingsPath) {
396
+ return path.dirname(settingsPath)
397
+ }
398
+
399
+ function migrateStoredLabel(agent) {
400
+ // Configs saved when the pi harness was branded "Pi (OpenMA)" keep
401
+ // working; only the stale default display label migrates to the id.
402
+ return agent.id === PI_ACP_ID && agent.label === PI_ACP_LEGACY_LABEL
403
+ ? { ...agent, label: PI_ACP_ID }
404
+ : agent
405
+ }
406
+
407
+ function configuredHarnesses(settingsPath) {
408
+ // enabled=false is Backchat's disable flag: the definition stays stored
409
+ // but a disabled agent must not be selectable or launchable here.
410
+ return readCustomAcpAgents(settingsHome(settingsPath))
411
+ .filter((agent) => agent.enabled !== false)
412
+ .map(migrateStoredLabel)
413
+ }
414
+
415
+ function settingsWithoutHarnesses(settings) {
416
+ const { harnesses: _legacyHarnesses, ...rest } = settings
417
+ return rest
395
418
  }
396
419
 
397
420
  function withoutLegacyActive(settings) {
@@ -414,36 +437,37 @@ function persistedDefaultId(settings) {
414
437
 
415
438
  export function upsertHarness(settingsPath, harness) {
416
439
  const next = validateHarness(harness)
417
- const settings = readSettings(settingsPath)
418
- const harnesses = configuredHarnesses(settings)
419
- const index = harnesses.findIndex(({ id }) => id === next.id)
420
- if (index === -1) harnesses.push(next)
421
- else harnesses[index] = next
422
- writeSettings(settingsPath, { ...withoutLegacyActive(settings), harnesses })
440
+ // The shared write migrates legacy harnesses[] into config.toml and
441
+ // strips the key; read settings after it so a stale copy cannot race
442
+ // the migrated file back in.
443
+ upsertCustomAcpAgent(settingsHome(settingsPath), next)
444
+ const settings = withoutLegacyActive(readSettings(settingsPath))
445
+ writeSettings(settingsPath, settingsWithoutHarnesses(settings))
423
446
  return next
424
447
  }
425
448
 
426
449
  export function setDefaultHarness(settingsPath, id) {
427
- const settings = readSettings(settingsPath)
428
- const harnesses = configuredHarnesses(settings)
450
+ const harnesses = configuredHarnesses(settingsPath)
429
451
  if (!harnesses.some((harness) => harness.id === id)) {
430
452
  throw new Error(`unknown harness ${JSON.stringify(id)}`)
431
453
  }
432
- writeSettings(settingsPath, { ...withoutLegacyActive(settings), harnesses, defaultHarness: id })
454
+ const settings = withoutLegacyActive(readSettings(settingsPath))
455
+ migrateLegacyAcpAgents(settingsHome(settingsPath))
456
+ writeSettings(settingsPath, { ...settingsWithoutHarnesses(settings), defaultHarness: id })
433
457
  }
434
458
 
435
459
  export function savedHarnesses(settingsPath) {
436
- return configuredHarnesses(readSettings(settingsPath))
460
+ return configuredHarnesses(settingsPath)
437
461
  }
438
462
 
439
463
  export function removeHarnessConfiguration(settingsPath, expected) {
440
- const settings = withoutLegacyActive(readSettings(settingsPath))
441
- const harnesses = configuredHarnesses(settings)
442
- const current = harnesses.find(({ id }) => id === expected.id)
464
+ const current = configuredHarnesses(settingsPath).find(({ id }) => id === expected.id)
443
465
  if (JSON.stringify(current) !== JSON.stringify(expected)) throw new Error('Harness configuration changed; review removal again')
444
- settings.harnesses = harnesses.filter(({ id }) => id !== expected.id)
445
- if (settings.defaultHarness === expected.id) delete settings.defaultHarness
446
- writeSettings(settingsPath, settings)
466
+ removeCustomAcpAgent(settingsHome(settingsPath), expected.id)
467
+ const settings = withoutLegacyActive(readSettings(settingsPath))
468
+ const rest = settingsWithoutHarnesses(settings)
469
+ if (rest.defaultHarness === expected.id) delete rest.defaultHarness
470
+ writeSettings(settingsPath, rest)
447
471
  }
448
472
 
449
473
  // Kept as a source-compatible alias for integrations compiled against 0.2.30.
@@ -454,7 +478,7 @@ export function selectedHarness(settingsPath) {
454
478
  const settings = readSettings(settingsPath)
455
479
  const id = persistedDefaultId(settings)
456
480
  if (typeof id !== 'string') return undefined
457
- return configuredHarnesses(settings).find((harness) => harness.id === id)
481
+ return configuredHarnesses(settingsPath).find((harness) => harness.id === id)
458
482
  }
459
483
 
460
484
  function executableFile(command) {
@@ -1010,7 +1034,7 @@ export function discoverHarnesses(settingsPath, options = {}) {
1010
1034
  }
1011
1035
 
1012
1036
  function discoverHarnessEntries(settingsPath, options, registryEntries) {
1013
- const configured = configuredHarnesses(readSettings(settingsPath))
1037
+ const configured = configuredHarnesses(settingsPath)
1014
1038
  .map((harness) => ({ ...harness, source: 'configured' }))
1015
1039
  const defaults = (options.defaults ?? []).map((entry) => ({
1016
1040
  ...validateHarness(entry),
@@ -1077,7 +1101,7 @@ function searchScore(entry, query = '') {
1077
1101
  }
1078
1102
 
1079
1103
  function findHarnessCandidates(settingsPath, options = {}, query = '') {
1080
- const configured = configuredHarnesses(readSettings(settingsPath)).map((entry) => ({
1104
+ const configured = configuredHarnesses(settingsPath).map((entry) => ({
1081
1105
  ...entry,
1082
1106
  source: 'configured',
1083
1107
  status: 'Configured',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "martty",
3
- "version": "0.3.2-beta.3",
3
+ "version": "0.3.2",
4
4
  "description": "Terminal-native ACP client UI; Cordis client tree, any ACP agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -77,7 +77,8 @@
77
77
  "dependencies": {
78
78
  "@deepseek-ai/cordis": "^4.0.1",
79
79
  "@lobehub/icons-static-svg": "1.95.0",
80
- "@openma/deepseek-harness-acp": "0.4.38-beta.1",
80
+ "@openma/common": "^0.9.2",
81
+ "@openma/deepseek-harness-acp": "0.4.38",
81
82
  "@resvg/resvg-js": "2.6.2",
82
83
  "cross-spawn": "^7.0.6",
83
84
  "node-downloader-helper": "2.1.11"
@@ -97,10 +98,10 @@
97
98
  "agent-client-protocol"
98
99
  ],
99
100
  "optionalDependencies": {
100
- "@openma/martty-darwin-arm64": "0.3.2-beta.3",
101
- "@openma/martty-darwin-x64": "0.3.2-beta.3",
102
- "@openma/martty-linux-x64": "0.3.2-beta.3",
103
- "@openma/martty-linux-arm64": "0.3.2-beta.3",
104
- "@openma/martty-win32-x64": "0.3.2-beta.3"
101
+ "@openma/martty-darwin-arm64": "0.3.2",
102
+ "@openma/martty-darwin-x64": "0.3.2",
103
+ "@openma/martty-linux-x64": "0.3.2",
104
+ "@openma/martty-linux-arm64": "0.3.2",
105
+ "@openma/martty-win32-x64": "0.3.2"
105
106
  }
106
107
  }