@genspark/cli 1.6.0 → 1.7.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/dist/commands/aidrive-download.d.ts +23 -0
- package/dist/commands/aidrive-download.d.ts.map +1 -0
- package/dist/commands/aidrive-download.js +36 -0
- package/dist/commands/aidrive-download.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -8
- package/dist/index.js.map +1 -1
- package/dist/staleCache.d.ts +73 -0
- package/dist/staleCache.d.ts.map +1 -0
- package/dist/staleCache.js +121 -0
- package/dist/staleCache.js.map +1 -0
- package/dist/task-shim.d.ts.map +1 -1
- package/dist/task-shim.js +1 -0
- package/dist/task-shim.js.map +1 -1
- package/docs/skills.md +3 -3
- package/package.json +1 -1
- package/skills/gsk-aidrive/SKILL.md +11 -6
- package/skills/gsk-analyze-media/SKILL.md +3 -1
- package/skills/gsk-create-task/SKILL.md +1 -0
- package/skills/gsk-genteam/SKILL.md +14 -8
- package/skills/gsk-gmail/SKILL.md +1 -0
- package/skills/gsk-google-drive/SKILL.md +3 -3
- package/skills/gsk-outlook-email/SKILL.md +1 -0
- package/skills/gsk-phone-call/SKILL.md +11 -28
- package/skills/gsk-task-artifacts/SKILL.md +4 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ApiResponse } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Name the operands of `gsk aidrive download <ai-drive-path> [local-path]`:
|
|
4
|
+
* first is the source, second the destination. With `explicitDrivePath` from
|
|
5
|
+
* `--path`/`-p`, the first operand is the destination instead.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveAidriveDownloadOperands(operands: readonly string[], explicitDrivePath?: string): {
|
|
8
|
+
drivePath?: string;
|
|
9
|
+
localPath?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Accept either spelling of an AI-Drive location — `aidrive:///a/b.pdf` (how
|
|
13
|
+
* attachments arrive) or the plain `/a/b.pdf` — and return the plain path the
|
|
14
|
+
* API takes. A path without the scheme passes through untouched.
|
|
15
|
+
*/
|
|
16
|
+
export declare function normalizeDrivePath(pathOrUrl: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Pull the signed download URL out of a `get_readable_url` response.
|
|
19
|
+
* `data.result` is prose ("Readable URL for <path>: <url>") and not meant to
|
|
20
|
+
* be parsed; the structured value lives in `session_state.aidrive_result`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractReadableUrl(response: Pick<ApiResponse, 'status' | 'session_state'>): string | undefined;
|
|
23
|
+
//# sourceMappingURL=aidrive-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aidrive-download.d.ts","sourceRoot":"","sources":["../../src/commands/aidrive-download.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,iBAAiB,CAAC,EAAE,MAAM,GACzB;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ5C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,eAAe,CAAC,GACtD,MAAM,GAAG,SAAS,CAMpB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name the operands of `gsk aidrive download <ai-drive-path> [local-path]`:
|
|
3
|
+
* first is the source, second the destination. With `explicitDrivePath` from
|
|
4
|
+
* `--path`/`-p`, the first operand is the destination instead.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAidriveDownloadOperands(operands, explicitDrivePath) {
|
|
7
|
+
const bareOperands = operands.filter((operand) => operand.length > 0 && !operand.startsWith('-'));
|
|
8
|
+
if (explicitDrivePath) {
|
|
9
|
+
return { drivePath: explicitDrivePath, localPath: bareOperands[0] };
|
|
10
|
+
}
|
|
11
|
+
return { drivePath: bareOperands[0], localPath: bareOperands[1] };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Accept either spelling of an AI-Drive location — `aidrive:///a/b.pdf` (how
|
|
15
|
+
* attachments arrive) or the plain `/a/b.pdf` — and return the plain path the
|
|
16
|
+
* API takes. A path without the scheme passes through untouched.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeDrivePath(pathOrUrl) {
|
|
19
|
+
// The scheme normally carries an empty host, hence the slash run.
|
|
20
|
+
return pathOrUrl.replace(/^aidrive:\/+/, '/');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pull the signed download URL out of a `get_readable_url` response.
|
|
24
|
+
* `data.result` is prose ("Readable URL for <path>: <url>") and not meant to
|
|
25
|
+
* be parsed; the structured value lives in `session_state.aidrive_result`.
|
|
26
|
+
*/
|
|
27
|
+
export function extractReadableUrl(response) {
|
|
28
|
+
if (response.status !== 'ok')
|
|
29
|
+
return undefined;
|
|
30
|
+
const aidriveResult = response.session_state?.aidrive_result;
|
|
31
|
+
if (!aidriveResult || typeof aidriveResult !== 'object')
|
|
32
|
+
return undefined;
|
|
33
|
+
const url = aidriveResult.readable_url;
|
|
34
|
+
return typeof url === 'string' && url ? url : undefined;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=aidrive-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aidrive-download.js","sourceRoot":"","sources":["../../src/commands/aidrive-download.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,QAA2B,EAC3B,iBAA0B;IAE1B,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAClC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAC5D,CAAA;IACD,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;IACrE,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,kEAAkE;IAClE,OAAO,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAuD;IAEvD,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAC9C,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAA;IAC5D,IAAI,CAAC,aAAa,IAAI,OAAO,aAAa,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACzE,MAAM,GAAG,GAAI,aAAyC,CAAC,YAAY,CAAA;IACnE,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AACzD,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG;AAiRH,KAAK,oBAAoB,GAAG,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAA;AAEtE,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,oBAAoB,CAAA;CAC3B;AAED,kFAAkF;AAClF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EAAE,GACb,mBAAmB,EAAE,CAqBvB"}
|
package/dist/index.js
CHANGED
|
@@ -26,15 +26,19 @@ import * as os from 'os';
|
|
|
26
26
|
import * as pathModule from 'path';
|
|
27
27
|
import { createRequire } from 'module';
|
|
28
28
|
import { spawn } from 'child_process';
|
|
29
|
+
import { Readable } from 'stream';
|
|
30
|
+
import { pipeline } from 'stream/promises';
|
|
29
31
|
import { ApiClient, isAuthError, unauthenticatedRequest, } from './client.js';
|
|
30
32
|
import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, passthroughWarning, urlsParamItems, } from './localFiles.js';
|
|
31
33
|
import { setDebugEnabled, setOutputFormat, debug, info, warn, error as logError, output, } from './logger.js';
|
|
32
34
|
import { loadConfigFile, getConfigPath, loadToolsCache, normalizeBaseUrl, saveToolsCache, setConfigPathOverride, updateConfigFile, } from './config.js';
|
|
33
35
|
import { checkForUpdates } from './updater.js';
|
|
36
|
+
import { addRegisteredCommands, collectToolCommandTokens, staleCacheMissingToken, } from './staleCache.js';
|
|
34
37
|
import { shimLegacyTaskInvocation } from './task-shim.js';
|
|
35
38
|
import { getSandboxRuntime } from './runtime.js';
|
|
36
39
|
import { registerDesignCommand } from './commands/design.js';
|
|
37
40
|
import { collectUploadFiles, pickExistingUploadOperand, } from './commands/aidrive-upload.js';
|
|
41
|
+
import { resolveAidriveDownloadOperands, normalizeDrivePath, extractReadableUrl, } from './commands/aidrive-download.js';
|
|
38
42
|
import { parseLastSeenIndex, eventStdoutLine } from './commands/chat-events.js';
|
|
39
43
|
import { mediaItemsKeyFor, mediaSummaryLines } from './mediaSummary.js';
|
|
40
44
|
import { registerMeshCommand, getMeshInvocation, runMesh, } from './commands/mesh.js';
|
|
@@ -320,6 +324,8 @@ async function streamFileUpload(filePath, uploadUrl, headers, fileSize, method =
|
|
|
320
324
|
});
|
|
321
325
|
}
|
|
322
326
|
function formatFileSize(bytes) {
|
|
327
|
+
if (bytes >= 1024 ** 3)
|
|
328
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
|
|
323
329
|
if (bytes >= 1024 * 1024)
|
|
324
330
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
325
331
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -389,9 +395,13 @@ async function downloadToFile(url, savePath, client) {
|
|
|
389
395
|
const resp = await fetch(downloadUrl);
|
|
390
396
|
if (!resp.ok)
|
|
391
397
|
throw new Error(`Download failed: HTTP ${resp.status}`);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
if (!resp.body)
|
|
399
|
+
throw new Error('Download failed: empty response body');
|
|
400
|
+
// Stream to disk: AI-Drive reads reach GB scale, past what the sandbox's
|
|
401
|
+
// swap-less RAM tolerates as a single buffer.
|
|
402
|
+
await pipeline(Readable.fromWeb(resp.body), fs.createWriteStream(savePath));
|
|
403
|
+
const sizeBytes = fs.statSync(savePath).size;
|
|
404
|
+
info(`Downloaded ${formatFileSize(sizeBytes)} → ${savePath}`);
|
|
395
405
|
return pathModule.resolve(savePath);
|
|
396
406
|
}
|
|
397
407
|
function isLocalFilePath(value) {
|
|
@@ -817,12 +827,15 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
817
827
|
cmd.option('--image <values...>', 'Design only: append ordered local visual references');
|
|
818
828
|
cmd.option('--file <values...>', 'Design only: append ordered local context files');
|
|
819
829
|
}
|
|
820
|
-
// For the aidrive tool, add client-side options used
|
|
830
|
+
// For the aidrive tool, add client-side options used by the --local_file
|
|
831
|
+
// upload and by `download`'s local destination.
|
|
821
832
|
if (tool.name === 'aidrive') {
|
|
822
833
|
if (!Object.hasOwn(props, 'workspace')) {
|
|
823
834
|
cmd.option('--workspace <value>', 'AI Drive workspace: personal or public_brain');
|
|
824
835
|
}
|
|
825
|
-
cmd.option('--local_file <path>', '
|
|
836
|
+
cmd.option('--local_file <path>', 'upload: local file path to upload to AI Drive (supports files >5 MB; ' +
|
|
837
|
+
'alternative to --file_content). download: local destination path ' +
|
|
838
|
+
'(same as the second operand; default ./<basename of the drive path>)');
|
|
826
839
|
cmd.option('--override', 'Overwrite an existing file at the destination path (used with --local_file)', false);
|
|
827
840
|
}
|
|
828
841
|
// For sb-git: `--execute [dest]` turns `clone-url` from a snippet-print
|
|
@@ -1190,6 +1203,75 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
1190
1203
|
process.exit(1);
|
|
1191
1204
|
}
|
|
1192
1205
|
}
|
|
1206
|
+
// Copy an AI-Drive file OUT to local disk. Client-side because only
|
|
1207
|
+
// this process can write the local filesystem: resolve the path to a
|
|
1208
|
+
// signed URL, then save it with the same streaming helper `-o` uses.
|
|
1209
|
+
// Never forwarded to the server, which answers a CLI too old to
|
|
1210
|
+
// intercept it with the two-call equivalent.
|
|
1211
|
+
if (tool.name === 'aidrive' && args.action === 'download') {
|
|
1212
|
+
const operandCandidates = primaryArgValue !== undefined && primaryArgValue === args.action
|
|
1213
|
+
? invokedCmd.args.slice(1)
|
|
1214
|
+
: invokedCmd.args;
|
|
1215
|
+
const { drivePath: sourceOperand, localPath: operandDest } = resolveAidriveDownloadOperands(operandCandidates, typeof args.path === 'string' ? args.path : undefined);
|
|
1216
|
+
if (!sourceOperand) {
|
|
1217
|
+
logError('download needs the AI-Drive path: gsk aidrive download ' +
|
|
1218
|
+
'<ai-drive-path> [local-path] (or --path/-p). To save an ' +
|
|
1219
|
+
'external url INTO AI Drive instead, use ' +
|
|
1220
|
+
'`download_file --file_url <url>`.');
|
|
1221
|
+
process.exit(1);
|
|
1222
|
+
}
|
|
1223
|
+
// Attachments arrive spelled `aidrive:///a/b.pdf`; take that as-is
|
|
1224
|
+
// rather than making the caller strip the scheme.
|
|
1225
|
+
const drivePath = normalizeDrivePath(sourceOperand);
|
|
1226
|
+
const urlResult = await client.executeTool('aidrive', {
|
|
1227
|
+
action: 'get_readable_url',
|
|
1228
|
+
path: drivePath,
|
|
1229
|
+
workspace: args.workspace,
|
|
1230
|
+
share: args.share,
|
|
1231
|
+
});
|
|
1232
|
+
const readableUrl = extractReadableUrl(urlResult);
|
|
1233
|
+
if (!readableUrl) {
|
|
1234
|
+
logError(`Failed to resolve AI-Drive path ${drivePath}: ${urlResult.message}`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
const destPath = (typeof opts.local_file === 'string' ? opts.local_file : '') ||
|
|
1238
|
+
operandDest ||
|
|
1239
|
+
pathModule.basename(drivePath);
|
|
1240
|
+
const savedPath = await downloadToFile(readableUrl, destPath, client);
|
|
1241
|
+
output({
|
|
1242
|
+
status: 'ok',
|
|
1243
|
+
message: 'AI-Drive file downloaded to local disk',
|
|
1244
|
+
data: { ai_drive_path: drivePath, local_path: savedPath },
|
|
1245
|
+
});
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
// `download_file` saves an external URL INTO the drive and cannot copy
|
|
1249
|
+
// one out, yet it is the obvious spelling for "download a drive file"
|
|
1250
|
+
// and both attempts used to fail silently: its handler never consumes
|
|
1251
|
+
// `path`, and an `aidrive://` --file_url fails the server's URL
|
|
1252
|
+
// validation (16 consecutive no-op calls observed in #52631). Name
|
|
1253
|
+
// `download` instead of letting either shape exit 0 having done
|
|
1254
|
+
// nothing.
|
|
1255
|
+
if (tool.name === 'aidrive' && args.action === 'download_file') {
|
|
1256
|
+
const fileUrl = typeof args.file_url === 'string' ? args.file_url : '';
|
|
1257
|
+
if (fileUrl.startsWith('aidrive://')) {
|
|
1258
|
+
logError('download_file saves an external URL INTO AI Drive; it cannot ' +
|
|
1259
|
+
'copy one out. Use: gsk aidrive download ' +
|
|
1260
|
+
`${fileUrl} <local-path>`);
|
|
1261
|
+
process.exit(1);
|
|
1262
|
+
}
|
|
1263
|
+
if (!fileUrl) {
|
|
1264
|
+
const operandCandidates = primaryArgValue !== undefined && primaryArgValue === args.action
|
|
1265
|
+
? invokedCmd.args.slice(1)
|
|
1266
|
+
: invokedCmd.args;
|
|
1267
|
+
const { drivePath } = resolveAidriveDownloadOperands(operandCandidates, typeof args.path === 'string' ? args.path : undefined);
|
|
1268
|
+
logError('download_file needs `--file_url <external-url>` — it saves an ' +
|
|
1269
|
+
'external URL INTO AI Drive. To copy a drive file OUT to ' +
|
|
1270
|
+
'local disk: gsk aidrive download ' +
|
|
1271
|
+
`${drivePath || '<ai-drive-path>'} <local-path>`);
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1193
1275
|
// aidrive: handle --local_file for the upload action.
|
|
1194
1276
|
// Stream the file directly to AI Drive via the dedicated tool_cli
|
|
1195
1277
|
// endpoint (POST /api/tool_cli/aidrive/upload) — no blob middleman.
|
|
@@ -2164,8 +2246,8 @@ async function phoneCallAction(recipient, opts, invokedAs) {
|
|
|
2164
2246
|
info('=== Call Dispatched (async) ===');
|
|
2165
2247
|
info(` Recipient: ${data.recipient || recipient}`);
|
|
2166
2248
|
info(` Project ID: ${data.project_id}`);
|
|
2167
|
-
info(` Poll: gsk call
|
|
2168
|
-
info(` Stop: gsk call
|
|
2249
|
+
info(` Poll: gsk telephony call status ${data.project_id}`);
|
|
2250
|
+
info(` Stop: gsk telephony call hangup ${data.project_id}`);
|
|
2169
2251
|
output(result);
|
|
2170
2252
|
return;
|
|
2171
2253
|
}
|
|
@@ -2436,6 +2518,41 @@ async function main() {
|
|
|
2436
2518
|
const forceRefresh = globalOpts.refresh || false;
|
|
2437
2519
|
// Try to load tools from cache
|
|
2438
2520
|
let tools = forceRefresh ? null : loadToolsCache(baseUrl);
|
|
2521
|
+
// Needed both for the stale-cache check and for dynamic registration below.
|
|
2522
|
+
const vdProjectId = resolveVdProjectId();
|
|
2523
|
+
// Stale-cache self-heal (#51775): the server omits gatekeeper-gated tools
|
|
2524
|
+
// per user, so a cached tree written before a gate flipped ON rejects the
|
|
2525
|
+
// newly enabled command for up to the cache TTL. When argv clearly cannot
|
|
2526
|
+
// resolve against the cached tree, drop the cache and take the fetch
|
|
2527
|
+
// branch below — one manifest GET instead of a 24h blind window.
|
|
2528
|
+
// When the heal path drops the cache, keep the stale tree as a fallback:
|
|
2529
|
+
// a failed manifest GET must not leave the invocation with ZERO dynamic
|
|
2530
|
+
// commands (strictly worse than the stale cache it replaced).
|
|
2531
|
+
let staleFallbackTools = null;
|
|
2532
|
+
if (tools && globalOpts.apiKey) {
|
|
2533
|
+
let missing = null;
|
|
2534
|
+
try {
|
|
2535
|
+
const { operands } = program.parseOptions(process.argv.slice(2));
|
|
2536
|
+
const index = collectToolCommandTokens(tools, Boolean(vdProjectId));
|
|
2537
|
+
// phone-call / call-for-me dial by DEFAULT subcommand, so their second
|
|
2538
|
+
// token may be a recipient — never provably a miss. (`telephony call
|
|
2539
|
+
// <recipient>` is level-3 and outside the two-level check.)
|
|
2540
|
+
addRegisteredCommands(index, program.commands, [
|
|
2541
|
+
'phone-call',
|
|
2542
|
+
'call-for-me',
|
|
2543
|
+
]);
|
|
2544
|
+
missing = staleCacheMissingToken(index, operands);
|
|
2545
|
+
}
|
|
2546
|
+
catch {
|
|
2547
|
+
// Conservative: any hiccup in the pre-parse means we keep the cache.
|
|
2548
|
+
}
|
|
2549
|
+
if (missing) {
|
|
2550
|
+
debug(`'${missing}' is not in the cached command tree — bypassing the ` +
|
|
2551
|
+
'tools cache and refetching the manifest (stale-cache self-heal)');
|
|
2552
|
+
staleFallbackTools = tools;
|
|
2553
|
+
tools = null;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2439
2556
|
if (tools) {
|
|
2440
2557
|
debug(`Loaded ${tools.length} tools from cache`);
|
|
2441
2558
|
}
|
|
@@ -2475,12 +2592,18 @@ async function main() {
|
|
|
2475
2592
|
debug('No API key available, skipping tool fetch');
|
|
2476
2593
|
}
|
|
2477
2594
|
}
|
|
2595
|
+
// Self-heal refetch failed (network/auth): fall back to the stale cache
|
|
2596
|
+
// so this invocation still registers every previously known command.
|
|
2597
|
+
if (!tools && staleFallbackTools) {
|
|
2598
|
+
debug('Manifest refetch failed — falling back to the cached (possibly ' +
|
|
2599
|
+
'stale) command tree');
|
|
2600
|
+
tools = staleFallbackTools;
|
|
2601
|
+
}
|
|
2478
2602
|
// Register dynamic tool commands
|
|
2479
2603
|
// Skip phone_call — the hardcoded `phone-call` command (dial default
|
|
2480
2604
|
// subcommand) handles dialing with streaming UX the generic handler lacks.
|
|
2481
2605
|
// Hide the `vd` subcommand group entirely unless the user has opted in
|
|
2482
2606
|
// via config.vd_project_id or GSK_VD_PROJECT_ID env var.
|
|
2483
|
-
const vdProjectId = resolveVdProjectId();
|
|
2484
2607
|
if (tools) {
|
|
2485
2608
|
for (const tool of tools) {
|
|
2486
2609
|
if (tool.name === 'phone_call')
|