@ai_mind/dsh-a2a 1.1.4 → 1.3.0

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 CHANGED
@@ -115,7 +115,7 @@
115
115
  面板分三部分:
116
116
 
117
117
  1. **概览**:实时 Agent Card、JSON-RPC / HTTP+JSON 地址(一键复制)、流式/推送/扩展卡片能力、鉴权状态、运行状态与可复制的 `curl` / 官方 JS 客户端示例。
118
- 2. **配置表单**:直接编辑名称、描述、对外地址、挂载路径、流式/推送/扩展卡片、鉴权模式与令牌环境变量、访问令牌(写入后不再回显)、Agent 预设/模型/工作目录/沙箱/审批/并发/超时,以及请求体与任务保留限额。
118
+ 2. **配置表单**:直接编辑名称、描述、对外地址、挂载路径、流式/推送/扩展卡片、鉴权模式与令牌环境变量、访问令牌(写入后不再回显)、Agent 预设/模型/工作目录/工作区名称/沙箱/审批/并发/超时,以及请求体与任务保留限额。预设、模型提供方、模型和推理强度会从当前 Host 的 `agentPresets` / `llm` 服务动态列出选项;留空即使用部署默认,无法列出的自定义值仍可手动输入。
119
119
  3. **保存**:写入 profile 的 `cordis.patch.yml`(`a2a-server` 行),校验通过后插件自动重载;修改挂载路径后刷新页面即可。
120
120
 
121
121
  配置接口 `GET/PUT <mountPath>/panel/config` 始终位于 DSH Web 的 origin(独立端口模式下也不在 A2A 端口上),使用 **Harness Web 连接凭据**(不是 A2A token)鉴权:只有能打开 Settings 的已登录浏览器可以读写,远程 A2A 客户端即使知道路径也会收到 401。令牌字段是只写字段,GET 只返回“是否已配置”。
@@ -266,6 +266,14 @@ server:
266
266
 
267
267
  ---
268
268
 
269
+ ### 工作区分组
270
+
271
+ A2A 创建的 Harness 会话使用 `agent.cwd` 作为工作目录,GUI 按会话目录把会话归入已注册的 Workspace。如果该目录还不是工作区,会话就会显示在「未分组」。
272
+
273
+ - 想让 A2A 会话归入**已有工作区**:把 `agent.cwd` 指向该工作区目录即可,插件会复用该工作区并附加新会话。
274
+ - 想为专用目录**新建分组**:设置 `agent.workspaceTitle`(面板里的「工作区名称(自动分组)」),插件会在该目录注册一个同名工作区,并把之后创建的 A2A 会话附加进去。
275
+ - 已有历史会话不会自动追溯重排;新创建的 A2A 任务会出现在分组下。
276
+
269
277
  ## Agent 运行时映射
270
278
 
271
279
  ### 会话与并发
@@ -400,6 +408,7 @@ agent:
400
408
  reasoningEffort: null
401
409
  maxTokens: null
402
410
  cwd: null # 绝对路径;留空用 workingDirectory.defaultDirectory
411
+ workspaceTitle: null # 可选;为 cwd 注册 GUI 工作区并让 A2A 会话归入该分组
403
412
  sandboxMode: workspace-write
404
413
  approvalPolicy: never
405
414
  maxConcurrentTasks: 4
package/client.js CHANGED
@@ -108,6 +108,11 @@ window.__ModuleLoader__.load({
108
108
  fieldAgentProvider: '模型提供方',
109
109
  fieldAgentModel: '模型',
110
110
  fieldAgentReasoningEffort: '推理强度',
111
+ fieldAgentWorkspaceTitle: '工作区名称(自动分组)',
112
+ optionDefault: '(使用默认)',
113
+ defaultModel: '默认模型(留空时)',
114
+ modelPairHint: '模型提供方和模型必须同时选择,或都留空使用默认。',
115
+ workspaceHint: 'A2A 会话会归入 agent.cwd 对应的工作区;若该目录还不是工作区,填写“工作区名称”即可为该目录创建分组。',
111
116
  fieldAgentSandboxMode: '沙箱模式',
112
117
  fieldAgentApprovalPolicy: '审批策略',
113
118
  fieldAgentMaxConcurrentTasks: '最大并发任务',
@@ -220,6 +225,11 @@ window.__ModuleLoader__.load({
220
225
  fieldAgentProvider: 'Model provider',
221
226
  fieldAgentModel: 'Model',
222
227
  fieldAgentReasoningEffort: 'Reasoning effort',
228
+ fieldAgentWorkspaceTitle: 'Workspace title (auto-group)',
229
+ optionDefault: '(use default)',
230
+ defaultModel: 'Default model (when empty)',
231
+ modelPairHint: 'Choose both a provider and a model, or leave both empty for the default.',
232
+ workspaceHint: 'A2A sessions join the workspace that owns agent.cwd. Set a workspace title to create that workspace when the directory is not registered yet.',
223
233
  fieldAgentSandboxMode: 'Sandbox mode',
224
234
  fieldAgentApprovalPolicy: 'Approval policy',
225
235
  fieldAgentMaxConcurrentTasks: 'Max concurrent tasks',
@@ -369,6 +379,15 @@ window.__ModuleLoader__.load({
369
379
  return typeof label === 'string' && label !== key ? label : value
370
380
  }
371
381
 
382
+ function panelOptionsPath(panelPath, provider, model) {
383
+ const base = panelPath.replace(/\/config$/, '/options')
384
+ const params = new URLSearchParams()
385
+ if (provider) params.set('provider', provider)
386
+ if (model) params.set('model', model)
387
+ const query = params.toString()
388
+ return query ? `${base}?${query}` : base
389
+ }
390
+
372
391
  function draftFrom(panel) {
373
392
  const values = {}
374
393
  for (const field of panel && Array.isArray(panel.fields) ? panel.fields : []) values[field.path] = field.value
@@ -428,6 +447,37 @@ window.__ModuleLoader__.load({
428
447
  const [saveError, setSaveError] = React.useState(null)
429
448
  const [copied, setCopied] = React.useState(null)
430
449
  const [reloadKey, setReloadKey] = React.useState(0)
450
+ const [optionSets, setOptionSets] = React.useState({})
451
+ const optionsRequest = React.useRef(0)
452
+
453
+ const loadOptions = async (panelPath, provider, model, signal) => {
454
+ if (!panelPath) return
455
+ const requestId = ++optionsRequest.current
456
+ try {
457
+ const response = await fetch(panelOptionsPath(panelPath, provider, model), {
458
+ cache: 'no-store',
459
+ credentials: 'same-origin',
460
+ signal,
461
+ })
462
+ if (!response.ok) return
463
+ const data = await response.json()
464
+ if (requestId !== optionsRequest.current) return
465
+ setOptionSets(data)
466
+ if (provider && !model && Array.isArray(data.models) && data.models.length > 0) {
467
+ const preferred =
468
+ data.default && data.default.provider === provider && data.models.some((entry) => entry.value === data.default.model)
469
+ ? data.default.model
470
+ : data.models[0].value
471
+ setDraft((previous) => {
472
+ if (previous['agent.provider'] !== provider || previous['agent.model']) return previous
473
+ return { ...previous, 'agent.model': preferred, 'agent.reasoningEffort': '' }
474
+ })
475
+ void loadOptions(panelPath, provider, preferred)
476
+ }
477
+ } catch {
478
+ /* options are optional; the text inputs remain usable */
479
+ }
480
+ }
431
481
 
432
482
  React.useEffect(() => {
433
483
  const controller = new AbortController()
@@ -487,6 +537,10 @@ window.__ModuleLoader__.load({
487
537
  }
488
538
  setState({ status: 'ready', card, health, denied, error: null, panel, panelPath, panelError })
489
539
  setDraft(draftFrom(panel))
540
+ if (panel && panelPath) {
541
+ const fieldValue = (path) => panel.fields.find((field) => field.path === path)?.value
542
+ void loadOptions(panelPath, fieldValue('agent.provider'), fieldValue('agent.model'), controller.signal)
543
+ }
490
544
  } catch (error) {
491
545
  if (error && error.name === 'AbortError') return
492
546
  setState({ status: 'error', card: null, health: null, denied: false, error: String((error && error.message) || error), panel: null, panelPath: null, panelError: null })
@@ -573,11 +627,35 @@ window.__ModuleLoader__.load({
573
627
  const dirty = panel
574
628
  ? panel.fields.some((field) => !sameValue(field, draft[field.path])) || token !== '' || clearToken
575
629
  : false
630
+ const invalidModelPair = Boolean(draft['agent.provider']) !== Boolean(draft['agent.model'])
576
631
 
577
632
  const setField = (path, value) => {
578
- setDraft((previous) => ({ ...previous, [path]: value }))
633
+ setDraft((previous) => {
634
+ const next = { ...previous, [path]: value }
635
+ if (path === 'agent.provider') {
636
+ next['agent.model'] = ''
637
+ next['agent.reasoningEffort'] = ''
638
+ } else if (path === 'agent.model') {
639
+ if (value === '') next['agent.provider'] = ''
640
+ else if (!previous['agent.provider']) {
641
+ next['agent.provider'] = optionSets.provider ?? optionSets.default?.provider ?? ''
642
+ }
643
+ next['agent.reasoningEffort'] = ''
644
+ }
645
+ return next
646
+ })
579
647
  setSaved(false)
580
648
  setSaveError(null)
649
+ if (path === 'agent.provider') {
650
+ void loadOptions(state.panelPath, value ?? '', '')
651
+ } else if (path === 'agent.model') {
652
+ if (value === '') {
653
+ void loadOptions(state.panelPath, '', '')
654
+ } else {
655
+ const provider = draft['agent.provider'] || optionSets.provider || optionSets.default?.provider || ''
656
+ void loadOptions(state.panelPath, provider, value)
657
+ }
658
+ }
581
659
  }
582
660
 
583
661
  const save = async () => {
@@ -618,6 +696,26 @@ window.__ModuleLoader__.load({
618
696
  const renderControl = (field) => {
619
697
  const value = draft[field.path]
620
698
  const onChange = (event) => setField(field.path, event.target.value)
699
+ const dynamicOptions = field.optionsKey ? optionSets[field.optionsKey] : undefined
700
+ if (field.optionsKey && Array.isArray(dynamicOptions) && dynamicOptions.length > 0) {
701
+ const current = value == null ? '' : String(value)
702
+ const options =
703
+ !current || dynamicOptions.some((option) => String(option.value) === current)
704
+ ? dynamicOptions
705
+ : [{ value: current, label: `${current} (current)` }, ...dynamicOptions]
706
+ return h(
707
+ 'select',
708
+ { className: 'a2aui-select', value: current, onChange },
709
+ h('option', { value: '' }, t('optionDefault')),
710
+ options.map((option) =>
711
+ h(
712
+ 'option',
713
+ { key: option.value, value: option.value },
714
+ option.description ? `${option.label} — ${option.description}` : option.label,
715
+ ),
716
+ ),
717
+ )
718
+ }
621
719
  if (field.type === 'boolean') {
622
720
  return h(
623
721
  'label',
@@ -719,6 +817,15 @@ window.__ModuleLoader__.load({
719
817
  h('div', null, renderControl(field)),
720
818
  ),
721
819
  ),
820
+ group === 'agent' && optionSets.default
821
+ ? h(
822
+ 'p',
823
+ { className: 'a2aui-field-note' },
824
+ `${t('defaultModel')}: ${optionSets.default.provider ?? '?'} / ${optionSets.default.model ?? '?'}` +
825
+ (optionSets.default.reasoningEffort ? ` · ${optionSets.default.reasoningEffort}` : ''),
826
+ )
827
+ : null,
828
+ group === 'agent' ? h('p', { className: 'a2aui-field-note' }, t('workspaceHint')) : null,
722
829
  )
723
830
  return group === 'auth' ? [node, secretBlock] : [node]
724
831
  }),
@@ -727,11 +834,14 @@ window.__ModuleLoader__.load({
727
834
  { className: 'a2aui-form-foot' },
728
835
  h(
729
836
  'button',
730
- { type: 'button', className: 'a2aui-btn a2aui-btnPrimary', disabled: !dirty || saving, onClick: save },
837
+ { type: 'button', className: 'a2aui-btn a2aui-btnPrimary', disabled: !dirty || saving || invalidModelPair, onClick: save },
731
838
  saving ? t('saving') : t('save'),
732
839
  ),
733
840
  h('button', { type: 'button', className: 'a2aui-btn', onClick: () => setReloadKey((value) => value + 1) }, t('reload')),
734
841
  dirty ? h('span', { className: 'a2aui-status' }, t('dirty')) : null,
842
+ invalidModelPair
843
+ ? h('span', { className: 'a2aui-status', 'data-tone': 'warn' }, t('modelPairHint'))
844
+ : null,
735
845
  saved ? h('span', { className: 'a2aui-status', 'data-tone': 'ok' }, t('saved')) : null,
736
846
  saveError ? h('span', { className: 'a2aui-status', 'data-tone': 'error' }, `${t('saveError')}: ${saveError}`) : null,
737
847
  h('span', { className: 'a2aui-status' }, t('configHint')),
package/lib/bridge.js CHANGED
@@ -275,6 +275,7 @@ class ContextWorker {
275
275
  throw new Error(`A2A agent cwd must be an absolute path, got ${JSON.stringify(cwd)}`)
276
276
  }
277
277
  await mkdir(cwd, { recursive: true })
278
+ const workspace = await runtime.ensureWorkspace(cwd)
278
279
  const sessionId = `session-a2a-${newId()}`
279
280
  const agentOptions = runtime.agentOptions()
280
281
  const presets = runtime.ctx.get?.('agentPresets')
@@ -307,6 +308,13 @@ class ContextWorker {
307
308
  }
308
309
  this.#handle = handle
309
310
  this.#agent = handle.agent
311
+ if (workspace !== null) {
312
+ try {
313
+ await workspace.attachSession(handle.agent.id)
314
+ } catch (error) {
315
+ runtime.logger.warn(`[a2a] could not attach session ${handle.agent.id} to workspace ${workspace.path}: ${errorMessage(error)}`)
316
+ }
317
+ }
310
318
  try {
311
319
  runtime.applyAgentPolicy(handle.agent)
312
320
  } catch (error) {
@@ -361,6 +369,7 @@ export class AgentRuntime {
361
369
  #logger
362
370
  #workers = new Map()
363
371
  #sweepTimer = undefined
372
+ #workspacePromise = undefined
364
373
  #disposed = false
365
374
  #stats = { turns: 0, completed: 0, failed: 0, canceled: 0 }
366
375
 
@@ -422,6 +431,42 @@ export class AgentRuntime {
422
431
  return options
423
432
  }
424
433
 
434
+ /**
435
+ * Resolve the project (GUI workspace) that owns the agent working directory.
436
+ *
437
+ * An existing workspace is reused and its sessions are attached explicitly.
438
+ * When `agent.workspaceTitle` is set, a missing workspace is created for the
439
+ * directory so the GUI can group new A2A sessions under it.
440
+ *
441
+ * @param {string} cwd absolute agent working directory
442
+ * @returns {Promise<object|null>} the Workspace entity, or null when grouping is unavailable
443
+ */
444
+ async ensureWorkspace(cwd) {
445
+ if (this.#workspacePromise === undefined) {
446
+ this.#workspacePromise = (async () => {
447
+ const registry = this.#ctx.get?.('workspaceRegistry')
448
+ if (registry === undefined) {
449
+ if (this.#config.agent.workspaceTitle) {
450
+ this.#logger.warn('[a2a] agent.workspaceTitle is set but workspaceRegistry is unavailable')
451
+ }
452
+ return null
453
+ }
454
+ try {
455
+ const existing = await registry.resolveByPath(cwd)
456
+ if (existing !== undefined) return existing
457
+ if (!this.#config.agent.workspaceTitle) return null
458
+ const created = await registry.create(cwd, this.#config.agent.workspaceTitle)
459
+ this.#logger.info(`[a2a] workspace "${created.title}" registered for ${cwd}`)
460
+ return created
461
+ } catch (error) {
462
+ this.#logger.warn(`[a2a] cannot resolve or create a workspace for ${cwd}: ${errorMessage(error)}`)
463
+ return null
464
+ }
465
+ })()
466
+ }
467
+ return this.#workspacePromise
468
+ }
469
+
425
470
  /** @returns {Promise<string>} the absolute workspace directory for agents */
426
471
  async resolveCwd() {
427
472
  if (this.#config.agent.cwd) return this.#config.agent.cwd
package/lib/config.js CHANGED
@@ -96,6 +96,7 @@ export const DEFAULT_CONFIG = {
96
96
  reasoningEffort: null,
97
97
  maxTokens: null,
98
98
  cwd: null,
99
+ workspaceTitle: null,
99
100
  sandboxMode: 'workspace-write',
100
101
  approvalPolicy: 'never',
101
102
  maxConcurrentTasks: 4,
@@ -463,6 +464,9 @@ export function normalizeConfig(raw) {
463
464
  if (merged.agent.cwd !== null && !merged.agent.cwd.startsWith('/')) {
464
465
  fail('agent.cwd', 'must be an absolute path')
465
466
  }
467
+ merged.agent.workspaceTitle = expectNullableString(merged.agent.workspaceTitle, 'agent.workspaceTitle', {
468
+ allowEmpty: false,
469
+ })
466
470
  merged.agent.sandboxMode = expectEnum(merged.agent.sandboxMode, 'agent.sandboxMode', [
467
471
  'inherit',
468
472
  'read-only',
package/lib/panel.js CHANGED
@@ -49,10 +49,11 @@ const FIELDS = [
49
49
  { path: 'auth.tokenEnv', type: 'string', group: 'auth', nullable: true, placeholder: 'A2A_AUTH_TOKEN' },
50
50
  { path: 'auth.allowRemoteUnauthenticated', type: 'boolean', group: 'auth' },
51
51
  { path: 'agent.cwd', type: 'string', group: 'agent', nullable: true, placeholder: '/srv/a2a-workspace' },
52
- { path: 'agent.preset', type: 'string', group: 'agent', nullable: true },
53
- { path: 'agent.provider', type: 'string', group: 'agent', nullable: true },
54
- { path: 'agent.model', type: 'string', group: 'agent', nullable: true },
55
- { path: 'agent.reasoningEffort', type: 'string', group: 'agent', nullable: true },
52
+ { path: 'agent.workspaceTitle', type: 'string', group: 'agent', nullable: true, placeholder: 'A2A Agents' },
53
+ { path: 'agent.preset', type: 'string', group: 'agent', nullable: true, optionsKey: 'presets' },
54
+ { path: 'agent.provider', type: 'string', group: 'agent', nullable: true, optionsKey: 'providers' },
55
+ { path: 'agent.model', type: 'string', group: 'agent', nullable: true, optionsKey: 'models' },
56
+ { path: 'agent.reasoningEffort', type: 'string', group: 'agent', nullable: true, optionsKey: 'reasoning' },
56
57
  {
57
58
  path: 'agent.sandboxMode',
58
59
  type: 'enum',
@@ -175,6 +176,7 @@ function describe(config, entry, warnings) {
175
176
  ...(field.min === undefined ? {} : { min: field.min }),
176
177
  ...(field.max === undefined ? {} : { max: field.max }),
177
178
  ...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
179
+ ...(field.optionsKey === undefined ? {} : { optionsKey: field.optionsKey }),
178
180
  value: value === undefined ? null : value,
179
181
  }
180
182
  })
@@ -202,6 +204,23 @@ function describe(config, entry, warnings) {
202
204
  * @param {() => object} [options.health] live health snapshot
203
205
  * @returns {import('express').Router}
204
206
  */
207
+ /** First value of an express query parameter. */
208
+ function firstQuery(value) {
209
+ if (Array.isArray(value)) return value[0]
210
+ return typeof value === 'string' ? value : undefined
211
+ }
212
+
213
+ /** Resolve an optional Host call without letting a slow model catalog block the panel. */
214
+ function withTimeout(promise, fallback, ms) {
215
+ return Promise.race([
216
+ Promise.resolve(promise).catch(() => fallback),
217
+ new Promise((resolve) => {
218
+ const timer = setTimeout(() => resolve(fallback), ms)
219
+ timer.unref?.()
220
+ }),
221
+ ])
222
+ }
223
+
205
224
  function safeHealth(health, logger) {
206
225
  try {
207
226
  return health()
@@ -287,7 +306,7 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
287
306
  res.setHeader('Cache-Control', 'no-store')
288
307
  res.json({
289
308
  ok: true,
290
- plugin: { name: PLUGIN_NAME, version: '1.1.4' },
309
+ plugin: { name: PLUGIN_NAME, version: '1.3.0' },
291
310
  mountPath,
292
311
  ...payload,
293
312
  ...(typeof status === 'function' ? { server: status() } : {}),
@@ -295,6 +314,88 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
295
314
  })
296
315
  })
297
316
 
317
+ router.get('/options', async (req, res) => {
318
+ if (authorize(req, res)) return
319
+ const llm = ctx.get?.('llm')
320
+ const presetsService = ctx.get?.('agentPresets')
321
+ const defaults = ctx.get?.('agentDefaultModel')
322
+ let selection = null
323
+ try {
324
+ selection = defaults?.currentSelection?.() ?? null
325
+ } catch (error) {
326
+ logger.warn(`[a2a] cannot read the default model selection: ${errorMessage(error)}`)
327
+ }
328
+ const requestedProvider = firstQuery(req.query.provider)?.trim() || null
329
+ const requestedModel = firstQuery(req.query.model)?.trim() || null
330
+ const provider = requestedProvider ?? selection?.provider ?? null
331
+ const model = requestedModel ?? (provider === selection?.provider ? selection?.model : null) ?? null
332
+
333
+ let presets = []
334
+ try {
335
+ const listed = await withTimeout(presetsService?.list?.(), [], 3000)
336
+ presets = (listed ?? []).map((preset) => ({
337
+ value: preset.id,
338
+ label: preset.name ?? preset.id,
339
+ description: preset.broken ? `unavailable: ${preset.broken}` : (preset.description ?? null),
340
+ }))
341
+ } catch (error) {
342
+ logger.warn(`[a2a] cannot list agent presets: ${errorMessage(error)}`)
343
+ }
344
+
345
+ let providers = []
346
+ try {
347
+ providers = (llm?.listProviders?.() ?? []).map((entry) => ({
348
+ value: entry.id,
349
+ label: entry.name ?? entry.id,
350
+ }))
351
+ } catch (error) {
352
+ logger.warn(`[a2a] cannot list model providers: ${errorMessage(error)}`)
353
+ }
354
+
355
+ let models = []
356
+ if (provider !== null && typeof llm?.listModels === 'function') {
357
+ try {
358
+ const listed = await withTimeout(llm.listModels(provider), [], 4000)
359
+ models = (listed ?? []).map((entry) => ({
360
+ value: entry.id,
361
+ label: entry.name && entry.name !== entry.id ? `${entry.name} (${entry.id})` : entry.id,
362
+ description: entry.description ?? null,
363
+ }))
364
+ } catch (error) {
365
+ logger.warn(`[a2a] cannot list models for ${provider}: ${errorMessage(error)}`)
366
+ }
367
+ }
368
+
369
+ let reasoning = []
370
+ let reasoningDefault = null
371
+ if (provider !== null && model !== null && typeof llm?.resolveModelInfo === 'function') {
372
+ try {
373
+ const info = await withTimeout(llm.resolveModelInfo(provider, model), null, 4000)
374
+ reasoning = (info?.reasoning?.efforts ?? []).map((effort) => ({
375
+ value: effort.id,
376
+ label: effort.name ?? effort.id,
377
+ description: effort.description ?? null,
378
+ }))
379
+ reasoningDefault = info?.reasoning?.defaultEffort ?? null
380
+ } catch (error) {
381
+ logger.warn(`[a2a] cannot resolve reasoning options for ${provider}/${model}: ${errorMessage(error)}`)
382
+ }
383
+ }
384
+
385
+ res.setHeader('Cache-Control', 'no-store')
386
+ res.json({
387
+ ok: true,
388
+ presets,
389
+ providers,
390
+ models,
391
+ reasoning,
392
+ reasoningDefault,
393
+ default: selection,
394
+ provider,
395
+ model,
396
+ })
397
+ })
398
+
298
399
  router.put('/config', json, async (req, res) => {
299
400
  if (authorize(req, res)) return
300
401
  const configEditor = editor()
@@ -384,7 +485,7 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
384
485
  logger.info('[a2a] configuration updated from the settings panel')
385
486
  res.json({
386
487
  ok: true,
387
- plugin: { name: PLUGIN_NAME, version: '1.1.4' },
488
+ plugin: { name: PLUGIN_NAME, version: '1.3.0' },
388
489
  mountPath,
389
490
  ...describe(live, liveEntry, []),
390
491
  ...(typeof status === 'function' ? { server: status() } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai_mind/dsh-a2a",
3
- "version": "1.1.4",
3
+ "version": "1.3.0",
4
4
  "description": "Production A2A (Agent2Agent) v1.0 server for DeepSeek Harness agents: JSON-RPC + HTTP+JSON bindings, streaming, tasks, push notifications and signed Agent Cards.",
5
5
  "type": "module",
6
6
  "license": "MIT",