nexus-agentd 0.1.7 → 0.1.9

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.
@@ -1,5 +1,5 @@
1
1
  import type { AgentSessionRuntime, AgentSessionSink } from '../session-contract.js';
2
- import type { AgentdA2AConfig, AgentdAgentView, AgentdInputAttachment } from '../types.js';
2
+ import type { AgentdA2AConfig, AgentdAgentView, AgentdInputAttachment, AgentdPendingResponse } from '../types.js';
3
3
  export declare function probeA2AAgent(id: string, config: AgentdA2AConfig): Promise<AgentdAgentView>;
4
4
  export declare class A2AClientRuntime implements AgentSessionRuntime {
5
5
  private readonly config;
@@ -10,8 +10,6 @@ export declare class A2AClientRuntime implements AgentSessionRuntime {
10
10
  private taskId;
11
11
  private contextId;
12
12
  private prompting;
13
- private firstPrompt;
14
- private readonly instructions;
15
13
  private disposed;
16
14
  private activeController?;
17
15
  private readonly artifactCache;
@@ -19,7 +17,7 @@ export declare class A2AClientRuntime implements AgentSessionRuntime {
19
17
  constructor(config: AgentdA2AConfig, sink: AgentSessionSink, promptTimeoutMs: number);
20
18
  start(): Promise<void>;
21
19
  prompt(message: string, attachments?: AgentdInputAttachment[]): Promise<void>;
22
- respondPending(message: string, attachments?: AgentdInputAttachment[]): Promise<void>;
20
+ respondPending(response: AgentdPendingResponse | string, attachments?: AgentdInputAttachment[]): Promise<void>;
23
21
  cancel(): Promise<void>;
24
22
  dispose(): Promise<void>;
25
23
  private messageRequest;
@@ -1,7 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { Role, TaskState } from '@a2a-js/sdk';
3
3
  import { ClientFactory, DefaultAgentCardResolver, JsonRpcTransportFactory, RestTransportFactory } from '@a2a-js/sdk/client';
4
- import { buildAgentInstructions, composeInitialAgentPrompt } from '../agent-instructions.js';
5
4
  export async function probeA2AAgent(id, config) {
6
5
  const startedAt = Date.now();
7
6
  if (config.enabled === false)
@@ -44,8 +43,6 @@ export class A2AClientRuntime {
44
43
  taskId = '';
45
44
  contextId = '';
46
45
  prompting = false;
47
- firstPrompt = true;
48
- instructions;
49
46
  disposed = false;
50
47
  activeController;
51
48
  artifactCache = new Map();
@@ -54,7 +51,6 @@ export class A2AClientRuntime {
54
51
  this.config = config;
55
52
  this.sink = sink;
56
53
  this.promptTimeoutMs = promptTimeoutMs;
57
- this.instructions = buildAgentInstructions(config.instructions);
58
54
  }
59
55
  async start() {
60
56
  const resolver = createResolver(this.config);
@@ -82,16 +78,12 @@ export class A2AClientRuntime {
82
78
  this.prompting = true;
83
79
  this.sink.clearPending();
84
80
  this.sink.setState('running');
85
- const promptMessage = this.firstPrompt
86
- ? composeInitialAgentPrompt(this.instructions, message)
87
- : message;
88
- this.firstPrompt = false;
89
81
  const controller = new AbortController();
90
82
  this.activeController = controller;
91
83
  const timer = setTimeout(() => controller.abort(new Error('A2A message timed out')), Math.min(this.promptTimeoutMs, this.config.timeoutMs || this.promptTimeoutMs));
92
84
  timer.unref?.();
93
85
  try {
94
- const request = this.messageRequest(promptMessage, attachments);
86
+ const request = this.messageRequest(message, attachments);
95
87
  for await (const response of this.client.sendMessageStream(request, {
96
88
  signal: controller.signal
97
89
  })) {
@@ -112,10 +104,19 @@ export class A2AClientRuntime {
112
104
  this.prompting = false;
113
105
  }
114
106
  }
115
- async respondPending(message, attachments = []) {
107
+ async respondPending(response, attachments = []) {
116
108
  if (this.sink.state !== 'input_required') {
117
109
  throw new Error('A2A session is not waiting for input');
118
110
  }
111
+ const message = typeof response === 'string'
112
+ ? response
113
+ : response.action === 'cancel'
114
+ ? 'cancel'
115
+ : response.action === 'decline'
116
+ ? 'decline'
117
+ : response.message ?? response.optionId ?? '';
118
+ if (!message.trim())
119
+ throw new Error('A2A pending response is empty');
119
120
  this.sink.clearPending();
120
121
  await this.prompt(message, attachments);
121
122
  }
@@ -1,6 +1,6 @@
1
1
  import type { AgentDriver } from '../drivers/index.js';
2
2
  import type { AcpSessionSink } from '../session-contract.js';
3
- import type { AgentdInputAttachment } from '../types.js';
3
+ import type { AgentdInputAttachment, AgentdPendingResponse } from '../types.js';
4
4
  export declare class AcpProcessRuntime {
5
5
  private readonly driver;
6
6
  private readonly sink;
@@ -12,14 +12,12 @@ export declare class AcpProcessRuntime {
12
12
  private prompting;
13
13
  private readonly maxStderrChunkChars;
14
14
  private readonly promptTimeoutMs;
15
- private readonly instructions;
16
- private firstPrompt;
17
15
  private promptCapabilities;
18
16
  private inputDirectory?;
19
- constructor(driver: AgentDriver, sink: AcpSessionSink, maxStderrChunkChars?: number, promptTimeoutMs?: number, customInstructions?: string);
17
+ constructor(driver: AgentDriver, sink: AcpSessionSink, maxStderrChunkChars?: number, promptTimeoutMs?: number);
20
18
  start(workspace: string): Promise<void>;
21
19
  prompt(message: string, attachments?: AgentdInputAttachment[]): Promise<void>;
22
- respondPending(message: string, _attachments?: AgentdInputAttachment[]): Promise<void>;
20
+ respondPending(input: AgentdPendingResponse | string, _attachments?: AgentdInputAttachment[]): Promise<void>;
23
21
  cancel(): Promise<void>;
24
22
  dispose(): Promise<void>;
25
23
  private requestPermission;
@@ -4,7 +4,6 @@ import path from 'node:path';
4
4
  import { Readable, Writable } from 'node:stream';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import * as acp from '@agentclientprotocol/sdk';
7
- import { buildAgentInstructions, composeInitialAgentPrompt } from '../agent-instructions.js';
8
7
  export class AcpProcessRuntime {
9
8
  driver;
10
9
  sink;
@@ -16,11 +15,9 @@ export class AcpProcessRuntime {
16
15
  prompting = false;
17
16
  maxStderrChunkChars;
18
17
  promptTimeoutMs;
19
- instructions;
20
- firstPrompt = true;
21
18
  promptCapabilities = {};
22
19
  inputDirectory;
23
- constructor(driver, sink, maxStderrChunkChars = 16 * 1024, promptTimeoutMs = 30 * 60 * 1000, customInstructions) {
20
+ constructor(driver, sink, maxStderrChunkChars = 16 * 1024, promptTimeoutMs = 30 * 60 * 1000) {
24
21
  this.driver = driver;
25
22
  this.sink = sink;
26
23
  const limit = Number.isFinite(maxStderrChunkChars)
@@ -28,7 +25,6 @@ export class AcpProcessRuntime {
28
25
  : 16 * 1024;
29
26
  this.maxStderrChunkChars = Math.max(1, Math.min(16 * 1024, limit));
30
27
  this.promptTimeoutMs = Math.max(10_000, Math.trunc(promptTimeoutMs));
31
- this.instructions = buildAgentInstructions(customInstructions);
32
28
  }
33
29
  async start(workspace) {
34
30
  if (this.process)
@@ -90,14 +86,10 @@ export class AcpProcessRuntime {
90
86
  this.prompting = true;
91
87
  this.sink.clearPending();
92
88
  this.sink.setState('running');
93
- const promptMessage = this.firstPrompt
94
- ? composeInitialAgentPrompt(this.instructions, message)
95
- : message;
96
- this.firstPrompt = false;
97
89
  try {
98
90
  const response = await withTimeout(this.connection.agent.request(acp.methods.agent.session.prompt, {
99
91
  sessionId,
100
- prompt: await this.promptBlocks(promptMessage, attachments)
92
+ prompt: await this.promptBlocks(message, attachments)
101
93
  }), this.promptTimeoutMs, 'ACP prompt timed out');
102
94
  if (this.sink.state === 'canceled' || this.sink.state === 'failed') {
103
95
  return;
@@ -123,16 +115,47 @@ export class AcpProcessRuntime {
123
115
  this.prompting = false;
124
116
  }
125
117
  }
126
- async respondPending(message, _attachments = []) {
118
+ async respondPending(input, _attachments = []) {
119
+ const response = typeof input === 'string'
120
+ ? {
121
+ requestId: this.pendingInput?.request.id ??
122
+ this.pending?.request.id ??
123
+ '',
124
+ message: input
125
+ }
126
+ : input;
127
127
  if (this.pendingInput) {
128
- this.finishInput(message);
128
+ if (response.requestId !== this.pendingInput.request.id) {
129
+ throw new Error(`ACP pending request no longer matches: ${response.requestId}`);
130
+ }
131
+ this.finishInput(response.message ?? response.optionId ?? '', response.action);
129
132
  return;
130
133
  }
131
134
  const pending = this.pending;
132
135
  if (!pending)
133
136
  throw new Error('ACP session is not waiting for input');
134
- const normalized = message.trim().toLowerCase();
137
+ if (response.requestId !== pending.request.id) {
138
+ throw new Error(`ACP pending request no longer matches: ${response.requestId}`);
139
+ }
135
140
  const options = pending.request.options || [];
141
+ if (response.action === 'cancel' || response.action === 'decline') {
142
+ this.finishPermission({ outcome: { outcome: 'cancelled' } });
143
+ return;
144
+ }
145
+ if (response.action === 'accept') {
146
+ const option = options.find((item) => item.kind?.startsWith('allow')) ||
147
+ options.find((item) => !(item.kind?.startsWith('reject') ?? false));
148
+ if (!option) {
149
+ throw new Error('Permission request does not provide an allow option');
150
+ }
151
+ this.finishPermission({
152
+ outcome: { outcome: 'selected', optionId: option.id }
153
+ });
154
+ return;
155
+ }
156
+ const normalized = (response.optionId ?? response.message ?? '')
157
+ .trim()
158
+ .toLowerCase();
136
159
  if (['cancel', 'deny', 'reject', '拒绝', '取消', '不同意'].includes(normalized)) {
137
160
  this.finishPermission({ outcome: { outcome: 'cancelled' } });
138
161
  return;
@@ -277,16 +300,17 @@ export class AcpProcessRuntime {
277
300
  this.pendingInput = { request, params, resolve, timer };
278
301
  });
279
302
  }
280
- finishInput(message) {
303
+ finishInput(message, requestedAction) {
281
304
  const pending = this.pendingInput;
282
305
  if (!pending)
283
306
  return;
284
307
  const normalized = message.trim().toLowerCase();
285
308
  let response;
286
- if (['cancel', '取消'].includes(normalized)) {
309
+ if (requestedAction === 'cancel' || ['cancel', '取消'].includes(normalized)) {
287
310
  response = { action: 'cancel' };
288
311
  }
289
- else if (['decline', '拒绝', '不同意'].includes(normalized)) {
312
+ else if (requestedAction === 'decline' ||
313
+ ['decline', '拒绝', '不同意'].includes(normalized)) {
290
314
  response = { action: 'decline' };
291
315
  }
292
316
  else if (!isFormElicitation(pending.params)) {
package/dist/config.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { mkdir, open, readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { normalizeAgentInstructions } from './agent-instructions.js';
4
3
  import { agentdDriverKinds } from './types.js';
5
4
  export async function ensureAgentdConfig(filePath, options = {}) {
6
5
  const absolute = path.resolve(filePath);
@@ -98,12 +97,6 @@ export async function loadAgentdConfig(filePath) {
98
97
  workspaceRoots,
99
98
  maxRequestBytes: integerValue(value.maxRequestBytes, 'maxRequestBytes', 1024 * 1024, 1024, 16 * 1024 * 1024),
100
99
  maxAttachmentBytes: integerValue(value.maxAttachmentBytes, 'maxAttachmentBytes', 32 * 1024 * 1024, 1024, 64 * 1024 * 1024),
101
- artifactStoragePath: resolveConfigPath(absolute, optionalString(value.artifactStoragePath, 'artifactStoragePath') || './artifacts'),
102
- maxArtifactBytes: integerValue(value.maxArtifactBytes, 'maxArtifactBytes', 512 * 1024 * 1024, 1024, 16 * 1024 * 1024 * 1024),
103
- maxArtifactStorageBytes: integerValue(value.maxArtifactStorageBytes, 'maxArtifactStorageBytes', 4 * 1024 * 1024 * 1024, 1024 * 1024, Number.MAX_SAFE_INTEGER),
104
- maxPublishedArtifacts: integerValue(value.maxPublishedArtifacts, 'maxPublishedArtifacts', 4096, 1, 1_000_000),
105
- maxConcurrentArtifactPublishes: integerValue(value.maxConcurrentArtifactPublishes, 'maxConcurrentArtifactPublishes', 4, 1, 128),
106
- artifactTtlMs: integerValue(value.artifactTtlMs, 'artifactTtlMs', 24 * 60 * 60 * 1000, 60_000, 30 * 24 * 60 * 60 * 1000),
107
100
  maxEventsPerSession: integerValue(value.maxEventsPerSession, 'maxEventsPerSession', 2048, 64, 100_000),
108
101
  maxOutputChars: integerValue(value.maxOutputChars, 'maxOutputChars', 512 * 1024, 16 * 1024, 16 * 1024 * 1024),
109
102
  sessionTtlMs: integerValue(value.sessionTtlMs, 'sessionTtlMs', 24 * 60 * 60 * 1000, 60_000, 30 * 24 * 60 * 60 * 1000),
@@ -149,7 +142,6 @@ function parseAgent(id, value) {
149
142
  driver,
150
143
  name: optionalString(input.name, `agents.${id}.name`),
151
144
  description: optionalString(input.description, `agents.${id}.description`),
152
- instructions: normalizeAgentInstructions(input.instructions, `agents.${id}.instructions`),
153
145
  enabled: optionalBoolean(input.enabled, `agents.${id}.enabled`) ?? true,
154
146
  workspace: optionalString(input.workspace, `agents.${id}.workspace`),
155
147
  command: optionalString(input.command, `agents.${id}.command`),
@@ -205,7 +197,6 @@ function parseA2AAgent(id, input) {
205
197
  protocol: 'a2a',
206
198
  name: optionalString(input.name, `agents.${id}.name`),
207
199
  description: optionalString(input.description, `agents.${id}.description`),
208
- instructions: normalizeAgentInstructions(input.instructions, `agents.${id}.instructions`),
209
200
  enabled: optionalBoolean(input.enabled, `agents.${id}.enabled`) ?? true,
210
201
  agentCardUrl,
211
202
  agentUrl,
@@ -338,9 +329,6 @@ function validateHttpUrl(value, name) {
338
329
  throw new Error(`nexus-agentd ${name} must be an http(s) URL without credentials or a fragment`);
339
330
  }
340
331
  }
341
- function resolveConfigPath(configPath, value) {
342
- return path.resolve(path.dirname(configPath), value);
343
- }
344
332
  function secretsEqual(left, right) {
345
333
  return left.length === right.length && left === right;
346
334
  }
@@ -5,7 +5,6 @@ export interface AgentdAgentUpdate {
5
5
  driver?: AgentdDriverKind;
6
6
  name?: string;
7
7
  description?: string;
8
- instructions?: string;
9
8
  enabled?: boolean;
10
9
  workspace?: string;
11
10
  permissionPolicy?: PermissionPolicy;
@@ -2,7 +2,6 @@ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
2
  import { chmod, open, readFile, rename, rm } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { hashAdminPassword, validateAdminPassword, verifyAdminPassword } from './auth.js';
5
- import { normalizeAgentInstructions } from './agent-instructions.js';
6
5
  import { loadAgentdConfig } from './config.js';
7
6
  import { createDriverRegistry } from './drivers/index.js';
8
7
  import { agentdDriverKinds } from './types.js';
@@ -312,7 +311,6 @@ export class AgentdControlPlane {
312
311
  next.name = cleanString(update.name) || cleanString(previous.name);
313
312
  }
314
313
  }
315
- setOptionalInstructions(next, update.instructions ?? cleanString(previous.instructions));
316
314
  setOptionalString(next, 'description', update.description ?? cleanString(previous.description));
317
315
  agents[id] = next;
318
316
  raw.agents = agents;
@@ -450,7 +448,6 @@ export class AgentdControlPlane {
450
448
  protocol: 'a2a',
451
449
  name: config.name || id,
452
450
  description: config.description,
453
- instructions: config.instructions,
454
451
  enabled: config.enabled !== false,
455
452
  agentCardUrl: config.agentCardUrl || defaultAgentCardUrl(config.agentUrl),
456
453
  agentUrl: config.agentUrl,
@@ -469,7 +466,6 @@ export class AgentdControlPlane {
469
466
  driver: config.driver,
470
467
  name: config.name || id,
471
468
  description: config.description,
472
- instructions: config.instructions,
473
469
  enabled: config.enabled !== false,
474
470
  workspace: config.workspace || this.config.workspaceRoots[0] || '',
475
471
  permissionPolicy: config.permissionPolicy || 'ask',
@@ -611,19 +607,6 @@ function setOptionalString(target, key, value) {
611
607
  else
612
608
  delete target[key];
613
609
  }
614
- function setOptionalInstructions(target, value) {
615
- let text;
616
- try {
617
- text = normalizeAgentInstructions(value);
618
- }
619
- catch (error) {
620
- throw new ControlPlaneError(400, errorMessage(error));
621
- }
622
- if (text)
623
- target.instructions = text;
624
- else
625
- delete target.instructions;
626
- }
627
610
  function validHeaderName(value) {
628
611
  return /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(value);
629
612
  }