@agents-at-scale/ark 0.1.49 → 0.1.50

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.
@@ -119,15 +119,18 @@ const defaultArkServices = {
119
119
  k8sDeploymentName: 'ark-mcp',
120
120
  k8sDevDeploymentName: 'ark-mcp-devspace',
121
121
  },
122
+ // ark-broker replaces ark-cluster-memory (renamed in v0.1.49). The old release
123
+ // must be uninstalled first to avoid Helm ownership conflicts on shared
124
+ // resources like the ark-config-streaming ConfigMap.
122
125
  'ark-broker': {
123
126
  name: 'ark-broker',
124
127
  helmReleaseName: 'ark-broker',
125
128
  description: 'In-memory storage service with streaming support for Ark queries',
126
129
  enabled: true,
127
130
  category: 'service',
128
- // namespace: undefined - uses current context namespace
129
131
  chartPath: `${REGISTRY_BASE}/ark-broker`,
130
132
  installArgs: [],
133
+ prerequisiteUninstalls: [{ releaseName: 'ark-cluster-memory' }],
131
134
  k8sDeploymentName: 'ark-broker',
132
135
  k8sDevDeploymentName: 'ark-broker-devspace',
133
136
  },
@@ -10,7 +10,19 @@ import { printNextSteps } from '../../lib/nextSteps.js';
10
10
  import ora from 'ora';
11
11
  import { waitForServicesReady, } from '../../lib/waitForReady.js';
12
12
  import { parseTimeoutToSeconds } from '../../lib/timeout.js';
13
+ async function uninstallPrerequisites(service, verbose = false) {
14
+ if (!service.prerequisiteUninstalls?.length)
15
+ return;
16
+ for (const prereq of service.prerequisiteUninstalls) {
17
+ const helmArgs = ['uninstall', prereq.releaseName, '--ignore-not-found'];
18
+ if (prereq.namespace) {
19
+ helmArgs.push('--namespace', prereq.namespace);
20
+ }
21
+ await execute('helm', helmArgs, { stdio: 'inherit' }, { verbose });
22
+ }
23
+ }
13
24
  async function installService(service, verbose = false) {
25
+ await uninstallPrerequisites(service, verbose);
14
26
  const helmArgs = [
15
27
  'upgrade',
16
28
  '--install',
@@ -11,7 +11,7 @@ export class KubernetesModelManifestBuilder {
11
11
  name: this.modelName,
12
12
  },
13
13
  spec: {
14
- type: config.type,
14
+ provider: config.type, // Use provider field (required as of v0.50.0)
15
15
  model: {
16
16
  value: config.modelValue,
17
17
  },
@@ -26,8 +26,8 @@ async function getQuery(name, options) {
26
26
  try {
27
27
  const query = await getResource('queries', name);
28
28
  if (options.response) {
29
- if (query.status?.responses && query.status.responses.length > 0) {
30
- const response = query.status.responses[0];
29
+ if (query.status?.response) {
30
+ const response = query.status.response;
31
31
  if (options.output === 'markdown') {
32
32
  console.log(renderMarkdown(response.content || ''));
33
33
  }
@@ -40,8 +40,8 @@ async function getQuery(name, options) {
40
40
  }
41
41
  }
42
42
  else if (options.output === 'markdown') {
43
- if (query.status?.responses && query.status.responses.length > 0) {
44
- console.log(renderMarkdown(query.status.responses[0].content || ''));
43
+ if (query.status?.response) {
44
+ console.log(renderMarkdown(query.status.response.content || ''));
45
45
  }
46
46
  else {
47
47
  output.warning('No response available');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,167 @@
1
+ import { jest } from '@jest/globals';
2
+ import output from '../../lib/output.js';
3
+ const mockExeca = jest.fn();
4
+ jest.unstable_mockModule('execa', () => ({
5
+ execa: mockExeca,
6
+ }));
7
+ const { createQueriesCommand } = await import('./index.js');
8
+ describe('queries get command', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ console.log = jest.fn();
12
+ jest.spyOn(output, 'warning').mockImplementation(() => { });
13
+ jest.spyOn(output, 'error').mockImplementation(() => { });
14
+ jest.spyOn(process, 'exit').mockImplementation(() => undefined);
15
+ });
16
+ it('should get query with response in JSON format', async () => {
17
+ const mockQuery = {
18
+ metadata: {
19
+ name: 'test-query',
20
+ },
21
+ spec: {
22
+ input: 'test input',
23
+ target: { type: 'agent', name: 'test-agent' },
24
+ },
25
+ status: {
26
+ phase: 'done',
27
+ response: {
28
+ content: 'This is the response',
29
+ },
30
+ },
31
+ };
32
+ mockExeca.mockResolvedValue({
33
+ stdout: JSON.stringify(mockQuery),
34
+ });
35
+ const command = createQueriesCommand({});
36
+ await command.parseAsync(['node', 'test', 'get', 'test-query']);
37
+ expect(console.log).toHaveBeenCalledWith(JSON.stringify(mockQuery, null, 2));
38
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', 'test-query', '-o', 'json'], { stdio: 'pipe' });
39
+ });
40
+ it('should get query with response flag in JSON format', async () => {
41
+ const mockQuery = {
42
+ metadata: {
43
+ name: 'test-query',
44
+ },
45
+ spec: {
46
+ input: 'test input',
47
+ target: { type: 'agent', name: 'test-agent' },
48
+ },
49
+ status: {
50
+ phase: 'done',
51
+ response: {
52
+ content: 'This is the response content',
53
+ },
54
+ },
55
+ };
56
+ mockExeca.mockResolvedValue({
57
+ stdout: JSON.stringify(mockQuery),
58
+ });
59
+ const command = createQueriesCommand({});
60
+ await command.parseAsync([
61
+ 'node',
62
+ 'test',
63
+ 'get',
64
+ 'test-query',
65
+ '--response',
66
+ ]);
67
+ expect(console.log).toHaveBeenCalledWith(JSON.stringify(mockQuery.status.response, null, 2));
68
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', 'test-query', '-o', 'json'], { stdio: 'pipe' });
69
+ });
70
+ it('should get query with response flag in markdown format', async () => {
71
+ const mockQuery = {
72
+ metadata: {
73
+ name: 'test-query',
74
+ },
75
+ spec: {
76
+ input: 'test input',
77
+ target: { type: 'agent', name: 'test-agent' },
78
+ },
79
+ status: {
80
+ phase: 'done',
81
+ response: {
82
+ content: '# Heading\n\nThis is markdown content',
83
+ },
84
+ },
85
+ };
86
+ mockExeca.mockResolvedValue({
87
+ stdout: JSON.stringify(mockQuery),
88
+ });
89
+ const command = createQueriesCommand({});
90
+ await command.parseAsync([
91
+ 'node',
92
+ 'test',
93
+ 'get',
94
+ 'test-query',
95
+ '--response',
96
+ '--output',
97
+ 'markdown',
98
+ ]);
99
+ expect(console.log).toHaveBeenCalled();
100
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', 'test-query', '-o', 'json'], { stdio: 'pipe' });
101
+ });
102
+ it('should get query in markdown format without response flag', async () => {
103
+ const mockQuery = {
104
+ metadata: {
105
+ name: 'test-query',
106
+ },
107
+ spec: {
108
+ input: 'test input',
109
+ target: { type: 'agent', name: 'test-agent' },
110
+ },
111
+ status: {
112
+ phase: 'done',
113
+ response: {
114
+ content: '# Response\n\nMarkdown response',
115
+ },
116
+ },
117
+ };
118
+ mockExeca.mockResolvedValue({
119
+ stdout: JSON.stringify(mockQuery),
120
+ });
121
+ const command = createQueriesCommand({});
122
+ await command.parseAsync([
123
+ 'node',
124
+ 'test',
125
+ 'get',
126
+ 'test-query',
127
+ '--output',
128
+ 'markdown',
129
+ ]);
130
+ expect(console.log).toHaveBeenCalled();
131
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', 'test-query', '-o', 'json'], { stdio: 'pipe' });
132
+ });
133
+ it('should warn when query has no response with response flag', async () => {
134
+ const mockQuery = {
135
+ metadata: {
136
+ name: 'test-query',
137
+ },
138
+ spec: {
139
+ input: 'test input',
140
+ target: { type: 'agent', name: 'test-agent' },
141
+ },
142
+ status: {
143
+ phase: 'running',
144
+ },
145
+ };
146
+ mockExeca.mockResolvedValue({
147
+ stdout: JSON.stringify(mockQuery),
148
+ });
149
+ const command = createQueriesCommand({});
150
+ await command.parseAsync([
151
+ 'node',
152
+ 'test',
153
+ 'get',
154
+ 'test-query',
155
+ '--response',
156
+ ]);
157
+ expect(output.warning).toHaveBeenCalledWith('No response available');
158
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', 'test-query', '-o', 'json'], { stdio: 'pipe' });
159
+ });
160
+ it('should handle errors when getting query', async () => {
161
+ mockExeca.mockRejectedValue(new Error('Query not found'));
162
+ const command = createQueriesCommand({});
163
+ await command.parseAsync(['node', 'test', 'get', 'nonexistent-query']);
164
+ expect(output.error).toHaveBeenCalledWith('fetching query:', 'Query not found');
165
+ expect(process.exit).toHaveBeenCalled();
166
+ });
167
+ });
@@ -467,11 +467,11 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
467
467
  }
468
468
  // Send message and get response with abort signal
469
469
  const fullResponse = await chatClientRef.current.sendMessage(target.id, apiMessages, { ...chatConfig, a2aContextId: a2aContextIdRef.current }, (chunk, toolCalls, arkMetadata) => {
470
- // Extract A2A context ID from first response
471
- // Chat TUI always queries a single target, so contextId is in responses[0]
472
- if (arkMetadata?.completedQuery?.status?.responses?.[0]?.a2a?.contextId) {
470
+ // Extract A2A context ID from response
471
+ // Chat TUI always queries a single target, so contextId is in response
472
+ if (arkMetadata?.completedQuery?.status?.response?.a2a?.contextId) {
473
473
  a2aContextIdRef.current =
474
- arkMetadata.completedQuery.status.responses[0].a2a.contextId;
474
+ arkMetadata.completedQuery.status.response.a2a.contextId;
475
475
  }
476
476
  // Update message progressively as chunks arrive
477
477
  setMessages((prev) => {
@@ -116,12 +116,10 @@ async function executeQueryWithFormat(options) {
116
116
  ...((options.conversationId || process.env.ARK_CONVERSATION_ID) && {
117
117
  conversationId: options.conversationId || process.env.ARK_CONVERSATION_ID,
118
118
  }),
119
- targets: [
120
- {
121
- type: options.targetType,
122
- name: options.targetName,
123
- },
124
- ],
119
+ target: {
120
+ type: options.targetType,
121
+ name: options.targetName,
122
+ },
125
123
  },
126
124
  };
127
125
  try {
@@ -101,7 +101,7 @@ export interface QueryResponse {
101
101
  export interface QueryStatus {
102
102
  phase?: 'initializing' | 'running' | 'done' | 'error' | 'canceled';
103
103
  conditions?: K8sCondition[];
104
- responses?: QueryResponse[];
104
+ response?: QueryResponse;
105
105
  message?: string;
106
106
  error?: string;
107
107
  tokenUsage?: {
@@ -120,7 +120,7 @@ export interface Query {
120
120
  metadata: K8sMetadata;
121
121
  spec?: {
122
122
  input: string;
123
- targets: QueryTarget[];
123
+ target: QueryTarget;
124
124
  sessionId?: string;
125
125
  conversationId?: string;
126
126
  timeout?: string;
@@ -1,3 +1,7 @@
1
+ export interface PrerequisiteUninstall {
2
+ releaseName: string;
3
+ namespace?: string;
4
+ }
1
5
  export interface ArkService {
2
6
  name: string;
3
7
  helmReleaseName: string;
@@ -7,6 +11,7 @@ export interface ArkService {
7
11
  namespace?: string;
8
12
  chartPath?: string;
9
13
  installArgs?: string[];
14
+ prerequisiteUninstalls?: PrerequisiteUninstall[];
10
15
  k8sServiceName?: string;
11
16
  k8sServicePort?: number;
12
17
  k8sPortForwardLocalPort?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-at-scale/ark",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "Ark CLI - Interactive terminal interface for ARK agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",