newo 3.7.4 → 3.7.5
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/CHANGELOG.md +15 -2
- package/README.md +70 -2
- package/dist/cli/commands/get-skill.d.ts +3 -0
- package/dist/cli/commands/get-skill.js +72 -0
- package/dist/cli/commands/help.js +29 -0
- package/dist/cli/commands/logs.d.ts +6 -1
- package/dist/cli/commands/logs.js +62 -14
- package/dist/cli/commands/sandbox.d.ts +10 -4
- package/dist/cli/commands/sandbox.js +182 -51
- package/dist/cli/commands/update-skill.d.ts +4 -0
- package/dist/cli/commands/update-skill.js +119 -0
- package/dist/cli.js +8 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +3 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +67 -19
- package/dist/sandbox/chat.d.ts +23 -3
- package/dist/sandbox/chat.js +83 -30
- package/dist/sync/remote-skill.d.ts +33 -0
- package/dist/sync/remote-skill.js +52 -0
- package/package.json +1 -1
- package/src/cli/commands/get-skill.ts +84 -0
- package/src/cli/commands/help.ts +29 -0
- package/src/cli/commands/logs.ts +83 -15
- package/src/cli/commands/sandbox.ts +238 -60
- package/src/cli/commands/update-skill.ts +139 -0
- package/src/cli.ts +10 -0
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +101 -20
- package/src/sandbox/chat.ts +106 -29
- package/src/sync/remote-skill.ts +92 -0
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sandbox Chat Command Handler
|
|
3
3
|
* Supports both single-command and interactive modes
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx newo sandbox "Hello" --customer <idn> # Single message mode
|
|
7
|
+
* npx newo sandbox --actor <actor_id> "Follow up" # Continue existing chat
|
|
8
|
+
* npx newo sandbox "ping" --connector vibe_agent # Select specific connector (v3.7.5)
|
|
9
|
+
* npx newo sandbox --list-connectors # Show running sandbox connectors (v3.7.5)
|
|
10
|
+
* npx newo sandbox --file ./msg.txt --actor <id> # Message from file (v3.7.5)
|
|
11
|
+
* cat msg.txt | npx newo sandbox --stdin # Message from stdin (v3.7.5)
|
|
12
|
+
* npx newo sandbox "ping" --timeout 420 # Custom response timeout in seconds (v3.7.5)
|
|
13
|
+
* npx newo sandbox "ping" --json # Machine-readable output (v3.7.5)
|
|
4
14
|
*/
|
|
5
15
|
|
|
6
|
-
import
|
|
16
|
+
import fs from 'fs-extra';
|
|
17
|
+
import type { MultiCustomerConfig, CliArgs, ConversationAct, SandboxChatSession } from '../../types.js';
|
|
7
18
|
import { makeClient } from '../../api.js';
|
|
8
19
|
import { getValidAccessToken } from '../../auth.js';
|
|
9
20
|
import { selectSingleCustomer } from '../customer-selection.js';
|
|
10
21
|
import { getChatHistory } from '../../api.js';
|
|
11
22
|
import {
|
|
12
23
|
findSandboxConnector,
|
|
24
|
+
listRunningSandboxConnectors,
|
|
13
25
|
createChatSession,
|
|
14
26
|
sendMessage,
|
|
15
27
|
pollForResponse,
|
|
@@ -17,19 +29,77 @@ import {
|
|
|
17
29
|
formatDebugInfo
|
|
18
30
|
} from '../../sandbox/chat.js';
|
|
19
31
|
|
|
32
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
33
|
+
|
|
34
|
+
interface SandboxOptions {
|
|
35
|
+
quiet: boolean;
|
|
36
|
+
json: boolean;
|
|
37
|
+
verbose: boolean;
|
|
38
|
+
timeoutMs: number;
|
|
39
|
+
integrationIdn: string | undefined;
|
|
40
|
+
connectorIdn: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SandboxJsonResult {
|
|
44
|
+
actor_id: string;
|
|
45
|
+
persona_id: string | null;
|
|
46
|
+
connector_idn: string;
|
|
47
|
+
external_event_id: string | null;
|
|
48
|
+
user_external_event_id: string | null;
|
|
49
|
+
agent_external_event_id: string | null;
|
|
50
|
+
response: string | null;
|
|
51
|
+
elapsed_ms: number;
|
|
52
|
+
timed_out: boolean;
|
|
53
|
+
flow_idn: string | null;
|
|
54
|
+
skill_idn: string | null;
|
|
55
|
+
session_id: string | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Normalize an act's external_event_id: the chat-history converter falls back
|
|
60
|
+
* to the placeholder 'chat_history' when the API omits the field.
|
|
61
|
+
*/
|
|
62
|
+
function actEventId(act: ConversationAct | null | undefined): string | null {
|
|
63
|
+
if (!act) return null;
|
|
64
|
+
const id = act.external_event_id;
|
|
65
|
+
return id && id !== 'chat_history' ? id : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read message text from --file, --stdin, or positional argument
|
|
70
|
+
*/
|
|
71
|
+
async function resolveMessage(args: CliArgs): Promise<string | null> {
|
|
72
|
+
if (args.file) {
|
|
73
|
+
const filePath = String(args.file);
|
|
74
|
+
if (!(await fs.pathExists(filePath))) {
|
|
75
|
+
throw new Error(`Message file not found: ${filePath}`);
|
|
76
|
+
}
|
|
77
|
+
return await fs.readFile(filePath, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (args.stdin) {
|
|
81
|
+
const chunks: Buffer[] = [];
|
|
82
|
+
for await (const chunk of process.stdin) {
|
|
83
|
+
chunks.push(Buffer.from(chunk));
|
|
84
|
+
}
|
|
85
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const messageArg = args._[1];
|
|
89
|
+
return messageArg === undefined ? null : String(messageArg);
|
|
90
|
+
}
|
|
91
|
+
|
|
20
92
|
/**
|
|
21
93
|
* Handle sandbox command
|
|
22
|
-
* Usage:
|
|
23
|
-
* npx newo sandbox "Hello" --customer <idn> # Single message mode
|
|
24
|
-
* npx newo sandbox --actor <actor_id> "Follow up" # Continue existing chat
|
|
25
|
-
* npx newo sandbox --interactive # Interactive mode (TBD)
|
|
26
94
|
*/
|
|
27
95
|
export async function handleSandboxCommand(
|
|
28
96
|
customerConfig: MultiCustomerConfig,
|
|
29
97
|
args: CliArgs,
|
|
30
98
|
verbose: boolean
|
|
31
99
|
): Promise<void> {
|
|
32
|
-
const
|
|
100
|
+
const json: boolean = Boolean(args.json);
|
|
101
|
+
// --json implies quiet logging: stdout must stay machine-readable
|
|
102
|
+
const quiet: boolean = Boolean(args.quiet || args.q) || json;
|
|
33
103
|
|
|
34
104
|
// Save original console functions
|
|
35
105
|
const originalConsoleLog = console.log;
|
|
@@ -68,6 +138,15 @@ export async function handleSandboxCommand(
|
|
|
68
138
|
console.warn = originalConsoleWarn;
|
|
69
139
|
}
|
|
70
140
|
|
|
141
|
+
const integrationIdn = args.integration ? String(args.integration) : undefined;
|
|
142
|
+
const connectorIdn = args.connector ? String(args.connector) : undefined;
|
|
143
|
+
|
|
144
|
+
// List running connectors and exit
|
|
145
|
+
if (args['list-connectors']) {
|
|
146
|
+
await listConnectorsCommand(client, integrationIdn, json);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
71
150
|
// Check for interactive mode
|
|
72
151
|
const interactive = args.interactive || args.i;
|
|
73
152
|
if (interactive) {
|
|
@@ -78,33 +157,44 @@ export async function handleSandboxCommand(
|
|
|
78
157
|
process.exit(1);
|
|
79
158
|
}
|
|
80
159
|
|
|
160
|
+
const timeoutSeconds = args.timeout ? parseFloat(String(args.timeout)) : DEFAULT_TIMEOUT_SECONDS;
|
|
161
|
+
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
162
|
+
if (!quiet) console.error(`❌ Invalid --timeout value: ${args.timeout} (expected positive number of seconds)`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const options: SandboxOptions = {
|
|
167
|
+
quiet,
|
|
168
|
+
json,
|
|
169
|
+
verbose: quiet ? false : verbose,
|
|
170
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
171
|
+
integrationIdn,
|
|
172
|
+
connectorIdn
|
|
173
|
+
};
|
|
174
|
+
|
|
81
175
|
// Check if continuing existing chat
|
|
82
176
|
const actorId = args.actor as string | undefined;
|
|
83
177
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!messageArg) {
|
|
178
|
+
const message = await resolveMessage(args);
|
|
179
|
+
if (message === null) {
|
|
87
180
|
if (!quiet) {
|
|
88
181
|
console.log('❌ Message is required');
|
|
89
|
-
console.log('Usage: npx newo sandbox "your message" [--actor <id>]');
|
|
90
|
-
console.log(' or: npx newo sandbox --actor <id>
|
|
182
|
+
console.log('Usage: npx newo sandbox "your message" [--actor <id>] [--connector <idn>]');
|
|
183
|
+
console.log(' or: npx newo sandbox --file <path> [--actor <id>]');
|
|
184
|
+
console.log(' or: cat msg.txt | npx newo sandbox --stdin [--actor <id>]');
|
|
91
185
|
}
|
|
92
186
|
process.exit(1);
|
|
93
187
|
}
|
|
94
188
|
|
|
95
|
-
// Convert to string (minimist may parse numbers)
|
|
96
|
-
const message = String(messageArg);
|
|
97
189
|
if (message.trim() === '') {
|
|
98
190
|
if (!quiet) console.log('❌ Message cannot be empty');
|
|
99
191
|
process.exit(1);
|
|
100
192
|
}
|
|
101
193
|
|
|
102
194
|
if (actorId) {
|
|
103
|
-
|
|
104
|
-
await continueExistingChat(client, actorId, message, verbose, quiet, originalConsoleLog, originalConsoleError, originalConsoleWarn);
|
|
195
|
+
await continueExistingChat(client, actorId, message, options, originalConsoleLog);
|
|
105
196
|
} else {
|
|
106
|
-
|
|
107
|
-
await startNewChat(client, message, verbose, quiet, originalConsoleLog, originalConsoleError, originalConsoleWarn);
|
|
197
|
+
await startNewChat(client, message, options, originalConsoleLog);
|
|
108
198
|
}
|
|
109
199
|
|
|
110
200
|
} catch (error: any) {
|
|
@@ -131,32 +221,103 @@ export async function handleSandboxCommand(
|
|
|
131
221
|
}
|
|
132
222
|
}
|
|
133
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Print running connectors of the (sandbox) integration
|
|
226
|
+
*/
|
|
227
|
+
async function listConnectorsCommand(
|
|
228
|
+
client: any,
|
|
229
|
+
integrationIdn: string | undefined,
|
|
230
|
+
asJson: boolean
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
const connectors = await listRunningSandboxConnectors(client, integrationIdn);
|
|
233
|
+
|
|
234
|
+
if (asJson) {
|
|
235
|
+
console.log(JSON.stringify(
|
|
236
|
+
connectors.map(c => ({
|
|
237
|
+
connector_idn: c.connector_idn,
|
|
238
|
+
integration_idn: c.integration_idn,
|
|
239
|
+
title: c.title,
|
|
240
|
+
status: c.status
|
|
241
|
+
})),
|
|
242
|
+
null,
|
|
243
|
+
2
|
|
244
|
+
));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (connectors.length === 0) {
|
|
249
|
+
console.log(`No running connectors found in integration '${integrationIdn || 'sandbox'}'`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(`🔌 Running connectors in integration '${integrationIdn || 'sandbox'}':\n`);
|
|
254
|
+
for (const c of connectors) {
|
|
255
|
+
console.log(` ${c.connector_idn}${c.title ? ` (${c.title})` : ''}`);
|
|
256
|
+
}
|
|
257
|
+
console.log(`\n💡 Use: npx newo sandbox "your message" --connector <connector_idn>`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Build and print the --json result object
|
|
262
|
+
*/
|
|
263
|
+
function printJsonResult(
|
|
264
|
+
session: SandboxChatSession,
|
|
265
|
+
acts: ConversationAct[],
|
|
266
|
+
userAct: ConversationAct | null,
|
|
267
|
+
elapsedMs: number,
|
|
268
|
+
print: typeof console.log
|
|
269
|
+
): void {
|
|
270
|
+
const agentAct = acts.find(a => a.is_agent) || null;
|
|
271
|
+
|
|
272
|
+
const jsonResult: SandboxJsonResult = {
|
|
273
|
+
actor_id: session.user_actor_id,
|
|
274
|
+
persona_id: session.user_persona_id !== 'unknown' ? session.user_persona_id : null,
|
|
275
|
+
connector_idn: session.connector_idn,
|
|
276
|
+
// external_event_id of the user turn is the correlation key for `newo logs --event-id`
|
|
277
|
+
external_event_id: actEventId(userAct),
|
|
278
|
+
user_external_event_id: actEventId(userAct),
|
|
279
|
+
agent_external_event_id: actEventId(agentAct),
|
|
280
|
+
response: agentAct ? (agentAct.source_text || agentAct.original_text || null) : null,
|
|
281
|
+
elapsed_ms: elapsedMs,
|
|
282
|
+
timed_out: agentAct === null,
|
|
283
|
+
flow_idn: agentAct && agentAct.flow_idn !== 'unknown' ? agentAct.flow_idn : null,
|
|
284
|
+
skill_idn: agentAct && agentAct.skill_idn !== 'unknown' ? agentAct.skill_idn : null,
|
|
285
|
+
session_id: agentAct && agentAct.session_id !== 'unknown' ? agentAct.session_id : null
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
print(JSON.stringify(jsonResult, null, 2));
|
|
289
|
+
}
|
|
290
|
+
|
|
134
291
|
/**
|
|
135
292
|
* Start a new sandbox chat and send a message
|
|
136
293
|
*/
|
|
137
294
|
async function startNewChat(
|
|
138
295
|
client: any,
|
|
139
296
|
message: string,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
originalConsoleLog: typeof console.log = console.log,
|
|
143
|
-
_originalConsoleError: typeof console.error = console.error,
|
|
144
|
-
_originalConsoleWarn: typeof console.warn = console.warn
|
|
297
|
+
options: SandboxOptions,
|
|
298
|
+
originalConsoleLog: typeof console.log
|
|
145
299
|
): Promise<void> {
|
|
300
|
+
const { quiet, json, verbose, timeoutMs } = options;
|
|
301
|
+
|
|
146
302
|
if (!quiet) console.log('🔧 Starting new sandbox chat...\n');
|
|
147
303
|
|
|
148
|
-
// Find sandbox connector
|
|
149
|
-
const
|
|
304
|
+
// Find sandbox connector (throws with available list when --connector not found)
|
|
305
|
+
const selection: { integrationIdn?: string; connectorIdn?: string } = {};
|
|
306
|
+
if (options.integrationIdn) selection.integrationIdn = options.integrationIdn;
|
|
307
|
+
if (options.connectorIdn) selection.connectorIdn = options.connectorIdn;
|
|
308
|
+
const connector = await findSandboxConnector(client, verbose, selection);
|
|
150
309
|
if (!connector) {
|
|
151
310
|
if (!quiet) {
|
|
152
311
|
console.error('❌ No running sandbox connector found');
|
|
153
312
|
console.error(' Please ensure you have a sandbox connector configured in your NEWO project');
|
|
313
|
+
} else if (json) {
|
|
314
|
+
originalConsoleLog(JSON.stringify({ error: 'No running sandbox connector found' }));
|
|
154
315
|
}
|
|
155
316
|
process.exit(1);
|
|
156
317
|
}
|
|
157
318
|
|
|
158
319
|
// Create chat session
|
|
159
|
-
const session = await createChatSession(client, connector,
|
|
320
|
+
const session = await createChatSession(client, connector, verbose);
|
|
160
321
|
|
|
161
322
|
if (!quiet) {
|
|
162
323
|
console.log(`\n📋 Chat Session Created:`);
|
|
@@ -165,16 +326,23 @@ async function startNewChat(
|
|
|
165
326
|
console.log(` Connector: ${session.connector_idn}`);
|
|
166
327
|
console.log(` External ID: ${session.external_id}\n`);
|
|
167
328
|
console.log(`📤 You: ${message}\n`);
|
|
168
|
-
} else {
|
|
329
|
+
} else if (!json) {
|
|
169
330
|
// In quiet mode, output Chat ID FIRST to stdout
|
|
170
331
|
originalConsoleLog(`CHAT_ID:${session.user_actor_id}`);
|
|
171
332
|
originalConsoleLog(`You: ${message}`);
|
|
172
333
|
}
|
|
173
334
|
|
|
174
|
-
const
|
|
335
|
+
const startedAt = Date.now();
|
|
336
|
+
const sentAt = await sendMessage(client, session, message, verbose);
|
|
175
337
|
|
|
176
338
|
// Poll for response
|
|
177
|
-
const { acts, agentPersonaId } = await pollForResponse(client, session, sentAt,
|
|
339
|
+
const { acts, agentPersonaId, userAct } = await pollForResponse(client, session, sentAt, verbose, timeoutMs);
|
|
340
|
+
const elapsedMs = Date.now() - startedAt;
|
|
341
|
+
|
|
342
|
+
if (json) {
|
|
343
|
+
printJsonResult(session, acts, userAct, elapsedMs, originalConsoleLog);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
178
346
|
|
|
179
347
|
if (acts.length === 0) {
|
|
180
348
|
if (!quiet) {
|
|
@@ -215,33 +383,34 @@ async function startNewChat(
|
|
|
215
383
|
return; // Exit early, showing only messages
|
|
216
384
|
}
|
|
217
385
|
|
|
218
|
-
// Display debug information
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
386
|
+
// Display debug information
|
|
387
|
+
if (verbose) {
|
|
388
|
+
console.log('\n📊 Debug Information:');
|
|
389
|
+
console.log(formatDebugInfo(acts));
|
|
390
|
+
console.log('');
|
|
391
|
+
} else {
|
|
392
|
+
// Show condensed debug info for single-command mode
|
|
393
|
+
console.log('📊 Debug Summary:');
|
|
394
|
+
const agentActs = acts.filter(a => a.is_agent);
|
|
395
|
+
if (agentActs.length > 0) {
|
|
396
|
+
const lastAct = agentActs[agentActs.length - 1];
|
|
397
|
+
if (lastAct) {
|
|
398
|
+
console.log(` Flow: ${lastAct.flow_idn || 'N/A'}`);
|
|
399
|
+
console.log(` Skill: ${lastAct.skill_idn || 'N/A'}`);
|
|
400
|
+
console.log(` Session: ${lastAct.session_id}`);
|
|
401
|
+
if (actEventId(userAct)) {
|
|
402
|
+
console.log(` Event ID (user turn): ${actEventId(userAct)}`);
|
|
234
403
|
}
|
|
235
|
-
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} system)`);
|
|
236
404
|
}
|
|
237
|
-
console.log(
|
|
405
|
+
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} system)`);
|
|
238
406
|
}
|
|
239
|
-
|
|
240
|
-
// Show continuation info
|
|
241
|
-
console.log(`💡 To continue this conversation:`);
|
|
242
|
-
console.log(` npx newo sandbox --actor ${session.user_actor_id} "your next message"`);
|
|
243
407
|
console.log('');
|
|
244
408
|
}
|
|
409
|
+
|
|
410
|
+
// Show continuation info
|
|
411
|
+
console.log(`💡 To continue this conversation:`);
|
|
412
|
+
console.log(` npx newo sandbox --actor ${session.user_actor_id} "your next message"`);
|
|
413
|
+
console.log('');
|
|
245
414
|
}
|
|
246
415
|
|
|
247
416
|
/**
|
|
@@ -251,12 +420,11 @@ async function continueExistingChat(
|
|
|
251
420
|
client: any,
|
|
252
421
|
actorId: string,
|
|
253
422
|
message: string,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
originalConsoleLog: typeof console.log = console.log,
|
|
257
|
-
_originalConsoleError: typeof console.error = console.error,
|
|
258
|
-
_originalConsoleWarn: typeof console.warn = console.warn
|
|
423
|
+
options: SandboxOptions,
|
|
424
|
+
originalConsoleLog: typeof console.log
|
|
259
425
|
): Promise<void> {
|
|
426
|
+
const { quiet, json, verbose, timeoutMs } = options;
|
|
427
|
+
|
|
260
428
|
if (!quiet) {
|
|
261
429
|
console.log(`💬 Continuing chat...`);
|
|
262
430
|
console.log(` Chat ID: ${actorId}\n`);
|
|
@@ -283,25 +451,32 @@ async function continueExistingChat(
|
|
|
283
451
|
}
|
|
284
452
|
|
|
285
453
|
// Create a temporary session for the existing chat
|
|
286
|
-
const session:
|
|
454
|
+
const session: SandboxChatSession = {
|
|
287
455
|
user_actor_id: actorId,
|
|
288
456
|
user_persona_id: 'unknown', // Not needed for continuation
|
|
289
457
|
agent_persona_id: null,
|
|
290
|
-
connector_idn: 'sandbox',
|
|
458
|
+
connector_idn: options.connectorIdn || 'sandbox',
|
|
291
459
|
session_id: null,
|
|
292
460
|
external_id: 'continuation'
|
|
293
461
|
};
|
|
294
462
|
|
|
295
463
|
// Send message (use original console in quiet mode)
|
|
296
464
|
if (quiet) {
|
|
297
|
-
originalConsoleLog(`You: ${message}`);
|
|
465
|
+
if (!json) originalConsoleLog(`You: ${message}`);
|
|
298
466
|
} else {
|
|
299
467
|
console.log(`📤 You: ${message}\n`);
|
|
300
468
|
}
|
|
301
|
-
const
|
|
469
|
+
const startedAt = Date.now();
|
|
470
|
+
const sentAt = await sendMessage(client, session, message, verbose);
|
|
302
471
|
|
|
303
472
|
// Poll for response using timestamp-based filtering
|
|
304
|
-
const { acts } = await pollForResponse(client, session, sentAt,
|
|
473
|
+
const { acts, userAct } = await pollForResponse(client, session, sentAt, verbose, timeoutMs);
|
|
474
|
+
const elapsedMs = Date.now() - startedAt;
|
|
475
|
+
|
|
476
|
+
if (json) {
|
|
477
|
+
printJsonResult(session, acts, userAct, elapsedMs, originalConsoleLog);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
305
480
|
|
|
306
481
|
if (acts.length === 0) {
|
|
307
482
|
if (!quiet) {
|
|
@@ -351,6 +526,9 @@ async function continueExistingChat(
|
|
|
351
526
|
console.log(` Flow: ${lastAct.flow_idn || 'N/A'}`);
|
|
352
527
|
console.log(` Skill: ${lastAct.skill_idn || 'N/A'}`);
|
|
353
528
|
console.log(` Session: ${lastAct.session_id}`);
|
|
529
|
+
if (actEventId(userAct)) {
|
|
530
|
+
console.log(` Event ID (user turn): ${actEventId(userAct)}`);
|
|
531
|
+
}
|
|
354
532
|
}
|
|
355
533
|
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} user)`);
|
|
356
534
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Skill Command Handler - Point edit of a skill directly on the platform
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* newo update-skill <skill-idn> --project <idn> --agent <idn> --flow <idn> \
|
|
6
|
+
* [--model <provider_idn>/<model_idn>] \
|
|
7
|
+
* [--script <file.nsl|file.guidance>] \
|
|
8
|
+
* [--publish] [--publish-description "<text>"]
|
|
9
|
+
*
|
|
10
|
+
* Unlike `newo push`, this changes exactly one skill without requiring a
|
|
11
|
+
* pulled workspace and without touching any other modified files. Typical
|
|
12
|
+
* use: temporarily switching a skill's model for an A/B test run.
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
16
|
+
import { makeClient, updateSkill, publishFlow } from '../../api.js';
|
|
17
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
18
|
+
import { resolveRemoteSkill, parseModelFlag } from '../../sync/remote-skill.js';
|
|
19
|
+
import { projectDir } from '../../fsutil.js';
|
|
20
|
+
import { v2ProjectDir } from '../../format/paths-v2.js';
|
|
21
|
+
import type { MultiCustomerConfig, CliArgs, Skill, PublishFlowRequest } from '../../types.js';
|
|
22
|
+
|
|
23
|
+
const USAGE = 'Usage: newo update-skill <skill-idn> --project <project-idn> --agent <agent-idn> --flow <flow-idn> [--model <provider>/<model>] [--script <file>] [--publish] [--publish-description "<text>"] [--customer <idn>]';
|
|
24
|
+
|
|
25
|
+
export async function findLocalProjectWorkspace(customerIdn: string, projectIdn: string): Promise<string | null> {
|
|
26
|
+
const candidates = [
|
|
27
|
+
projectDir(customerIdn, projectIdn),
|
|
28
|
+
v2ProjectDir(customerIdn, projectIdn)
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (await fs.pathExists(candidate)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function handleUpdateSkillCommand(
|
|
41
|
+
customerConfig: MultiCustomerConfig,
|
|
42
|
+
args: CliArgs,
|
|
43
|
+
verbose: boolean = false
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
const skillIdn = args._[1] as string | undefined;
|
|
46
|
+
const projectIdn = args.project as string | undefined;
|
|
47
|
+
const agentIdn = args.agent as string | undefined;
|
|
48
|
+
const flowIdn = args.flow as string | undefined;
|
|
49
|
+
const modelFlag = args.model as string | undefined;
|
|
50
|
+
const scriptFlag = args.script as string | undefined;
|
|
51
|
+
const shouldPublish = Boolean(args.publish);
|
|
52
|
+
const publishDescription = args['publish-description'] as string | undefined;
|
|
53
|
+
|
|
54
|
+
if (!skillIdn || !projectIdn || !agentIdn || !flowIdn) {
|
|
55
|
+
console.error('Error: skill IDN, --project, --agent and --flow are required');
|
|
56
|
+
console.error(USAGE);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!modelFlag && !scriptFlag) {
|
|
61
|
+
console.error('Error: nothing to update — pass --model and/or --script');
|
|
62
|
+
console.error(USAGE);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const newModel = modelFlag ? parseModelFlag(String(modelFlag)) : null;
|
|
67
|
+
|
|
68
|
+
let newScript: string | null = null;
|
|
69
|
+
if (scriptFlag) {
|
|
70
|
+
const scriptPath = String(scriptFlag);
|
|
71
|
+
if (!(await fs.pathExists(scriptPath))) {
|
|
72
|
+
console.error(`Error: script file not found: ${scriptPath}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
newScript = await fs.readFile(scriptPath, 'utf8');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
79
|
+
|
|
80
|
+
const token = await getValidAccessToken(selectedCustomer);
|
|
81
|
+
const client = await makeClient(verbose, token);
|
|
82
|
+
|
|
83
|
+
if (verbose) console.log(`🔍 Resolving skill ${projectIdn}/${agentIdn}/${flowIdn}/${skillIdn}...`);
|
|
84
|
+
|
|
85
|
+
const { project, agent, flow, skill } = await resolveRemoteSkill(client, {
|
|
86
|
+
projectIdn,
|
|
87
|
+
agentIdn,
|
|
88
|
+
flowIdn,
|
|
89
|
+
skillIdn
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Build updated skill object, preserving everything we don't change
|
|
93
|
+
const updatedSkill: Skill = {
|
|
94
|
+
...skill,
|
|
95
|
+
...(newModel ? { model: newModel } : {}),
|
|
96
|
+
...(newScript !== null ? { prompt_script: newScript } : {})
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
console.log(`✏️ Updating skill: ${project.idn}/${agent.idn}/${flow.idn}/${skill.idn} (${skill.id})`);
|
|
100
|
+
if (newModel) {
|
|
101
|
+
console.log(` Model: ${skill.model.provider_idn}/${skill.model.model_idn} → ${newModel.provider_idn}/${newModel.model_idn}`);
|
|
102
|
+
}
|
|
103
|
+
if (newScript !== null) {
|
|
104
|
+
console.log(` Script: ${(skill.prompt_script || '').length} chars → ${newScript.length} chars (from ${scriptFlag})`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await updateSkill(client, updatedSkill);
|
|
108
|
+
console.log('✅ Skill updated (draft)');
|
|
109
|
+
|
|
110
|
+
// Warn when a pulled local workspace exists: it now diverges from the platform
|
|
111
|
+
const localProjectDir = await findLocalProjectWorkspace(selectedCustomer.idn, project.idn);
|
|
112
|
+
if (localProjectDir) {
|
|
113
|
+
console.warn(`⚠️ Local workspace exists at ${localProjectDir} and now differs from the platform.`);
|
|
114
|
+
console.warn(` Run 'newo pull' to sync it, or remember to revert this change.`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (shouldPublish) {
|
|
118
|
+
const publishData: PublishFlowRequest = {
|
|
119
|
+
version: '1.0',
|
|
120
|
+
description: publishDescription || 'Published via NEWO CLI (update-skill)',
|
|
121
|
+
type: 'public'
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await publishFlow(client, flow.id, publishData);
|
|
126
|
+
console.log(`🚀 Flow published: ${flow.idn}`);
|
|
127
|
+
} catch (error: any) {
|
|
128
|
+
const errorMessage = error.response?.data?.message || error.message || 'Unknown error';
|
|
129
|
+
console.error(`❌ Failed to publish flow '${flow.idn}': ${errorMessage}`);
|
|
130
|
+
const errorDetails = error.response?.data?.reasons || error.response?.data?.errors || error.response?.data?.detail;
|
|
131
|
+
if (errorDetails) {
|
|
132
|
+
console.error(` Details: ${JSON.stringify(errorDetails)}`);
|
|
133
|
+
}
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`💡 Changes are draft-only. Add --publish to publish flow '${flow.idn}'.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { handleDeleteAgentCommand } from './cli/commands/delete-agent.js';
|
|
|
21
21
|
import { handleCreateFlowCommand } from './cli/commands/create-flow.js';
|
|
22
22
|
import { handleDeleteFlowCommand } from './cli/commands/delete-flow.js';
|
|
23
23
|
import { handleCreateSkillCommand } from './cli/commands/create-skill.js';
|
|
24
|
+
import { handleGetSkillCommand } from './cli/commands/get-skill.js';
|
|
25
|
+
import { handleUpdateSkillCommand } from './cli/commands/update-skill.js';
|
|
24
26
|
import { handleDeleteSkillCommand } from './cli/commands/delete-skill.js';
|
|
25
27
|
import { handleCreateProjectCommand } from './cli/commands/create-project.js';
|
|
26
28
|
import { handleCreateCustomerCommand } from './cli/commands/create-customer.js';
|
|
@@ -185,6 +187,14 @@ async function main(): Promise<void> {
|
|
|
185
187
|
await handleCreateSkillCommand(customerConfig, args, verbose);
|
|
186
188
|
break;
|
|
187
189
|
|
|
190
|
+
case 'get-skill':
|
|
191
|
+
await handleGetSkillCommand(customerConfig, args, verbose);
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case 'update-skill':
|
|
195
|
+
await handleUpdateSkillCommand(customerConfig, args, verbose);
|
|
196
|
+
break;
|
|
197
|
+
|
|
188
198
|
case 'delete-skill':
|
|
189
199
|
await handleDeleteSkillCommand(customerConfig, args, verbose);
|
|
190
200
|
break;
|