aui-agent-builder 0.3.103 → 0.3.104
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/api-client/index.d.ts +304 -19
- package/dist/api-client/index.d.ts.map +1 -1
- package/dist/api-client/index.js +337 -69
- package/dist/api-client/index.js.map +1 -1
- package/dist/commands/agents.d.ts +55 -1
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +193 -50
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/import-agent.d.ts +23 -0
- package/dist/commands/import-agent.d.ts.map +1 -1
- package/dist/commands/import-agent.js +802 -151
- package/dist/commands/import-agent.js.map +1 -1
- package/dist/commands/legacy/push-records-mode.d.ts +166 -0
- package/dist/commands/legacy/push-records-mode.d.ts.map +1 -0
- package/dist/commands/legacy/push-records-mode.js +2536 -0
- package/dist/commands/legacy/push-records-mode.js.map +1 -0
- package/dist/commands/pull-agent.d.ts +8 -0
- package/dist/commands/pull-agent.d.ts.map +1 -1
- package/dist/commands/pull-agent.js +567 -126
- package/dist/commands/pull-agent.js.map +1 -1
- package/dist/commands/push.d.ts +78 -107
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +942 -1804
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/util/agent-mode.d.ts +69 -0
- package/dist/commands/util/agent-mode.d.ts.map +1 -0
- package/dist/commands/util/agent-mode.js +101 -0
- package/dist/commands/util/agent-mode.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +23 -7
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/version-snapshot.d.ts.map +1 -1
- package/dist/commands/version-snapshot.js +253 -49
- package/dist/commands/version-snapshot.js.map +1 -1
- package/dist/commands/version.d.ts +15 -1
- package/dist/commands/version.d.ts.map +1 -1
- package/dist/commands/version.js +102 -7
- package/dist/commands/version.js.map +1 -1
- package/dist/config/index.d.ts +16 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/ui/views/ImportAgentView.d.ts +15 -0
- package/dist/ui/views/ImportAgentView.d.ts.map +1 -1
- package/dist/ui/views/ImportAgentView.js +8 -3
- package/dist/ui/views/ImportAgentView.js.map +1 -1
- package/dist/utils/index.d.ts +80 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +330 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { render } from "ink";
|
|
4
|
+
import { render, Box } from "ink";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import { getConfig, loadSession, loadProjectConfig, saveProjectConfig, findProjectRoot, loadAgentSettingsApiKey, saveAgentSettingsApiKey, } from "../config/index.js";
|
|
7
|
-
import { AUIClient } from "../api-client/index.js";
|
|
7
|
+
import { AUIClient, AUIAPIError } from "../api-client/index.js";
|
|
8
8
|
import { getTracer, SpanStatusCode, setUserContext } from "../telemetry.js";
|
|
9
|
-
import { AuthenticationError } from "../errors/index.js";
|
|
9
|
+
import { AuthenticationError, CLIError, ConfigError } from "../errors/index.js";
|
|
10
10
|
import { KBViewClient } from "../api-client/kb-view-client.js";
|
|
11
11
|
import { exportToFolder, buildScope, } from "../services/kb-view.service.js";
|
|
12
12
|
import { fetchSchemas } from "../services/pull-schema.service.js";
|
|
13
13
|
import { initAndCommitBaseline } from "../utils/git.js";
|
|
14
|
+
import { disassembleBundleToFiles, normalizeBundleFileBody, readBundleTools, } from "../utils/index.js";
|
|
14
15
|
import { Header, StatusLine, Spinner, ErrorDisplay, Hint, } from "../ui/components/index.js";
|
|
15
16
|
import { ImportWarnings } from "../ui/views/ImportAgentView.js";
|
|
17
|
+
import { detectAgentBundleMode, isModeMismatchError, } from "./util/agent-mode.js";
|
|
16
18
|
import { PullAgentView } from "../ui/views/PullAgentView.js";
|
|
17
19
|
// ─── Ink Rendering Helpers ───
|
|
18
20
|
function log(node) {
|
|
@@ -39,8 +41,35 @@ function startSpinner(label) {
|
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
|
-
// ───
|
|
43
|
-
|
|
44
|
+
// ─── Filename routing (server bundle → local folder layout) ───
|
|
45
|
+
//
|
|
46
|
+
// The push/pull endpoint disallows path separators in filenames, so tool
|
|
47
|
+
// files live at the root of the bundle (e.g. `web_search.aui.json`).
|
|
48
|
+
// Locally we keep them under `tools/<name>.aui.json` for ergonomics —
|
|
49
|
+
// every file in the response is routed to one of these two locations.
|
|
50
|
+
const CANONICAL_ROOT_FILES = new Set([
|
|
51
|
+
"agent.aui.json",
|
|
52
|
+
"parameters.aui.json",
|
|
53
|
+
"entities.aui.json",
|
|
54
|
+
"integrations.aui.json",
|
|
55
|
+
"rules.aui.json",
|
|
56
|
+
"tools.aui.json",
|
|
57
|
+
"manifest.json",
|
|
58
|
+
]);
|
|
59
|
+
function targetPathForBundleFile(projectRoot, filename) {
|
|
60
|
+
if (CANONICAL_ROOT_FILES.has(filename)) {
|
|
61
|
+
return path.join(projectRoot, filename);
|
|
62
|
+
}
|
|
63
|
+
// Server filenames have no path separators (enforced by the endpoint),
|
|
64
|
+
// so anything that isn't a canonical root file is a per-tool blob —
|
|
65
|
+
// write under `tools/`.
|
|
66
|
+
return path.join(projectRoot, "tools", filename);
|
|
67
|
+
}
|
|
68
|
+
// ─── Legacy normalizers (only the old export endpoints need these) ───
|
|
69
|
+
// Kept under a `Legacy` suffix to make it obvious from the call site
|
|
70
|
+
// which path you're on. New-pull blob bodies are pre-shaped server-side
|
|
71
|
+
// and don't need normalization.
|
|
72
|
+
function normalizeGeneralSettingsLegacy(raw) {
|
|
44
73
|
if (!raw)
|
|
45
74
|
return null;
|
|
46
75
|
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -50,7 +79,7 @@ function normalizeGeneralSettings(raw) {
|
|
|
50
79
|
}
|
|
51
80
|
return raw;
|
|
52
81
|
}
|
|
53
|
-
function
|
|
82
|
+
function normalizeWrappedLegacy(key, raw) {
|
|
54
83
|
if (!raw)
|
|
55
84
|
return null;
|
|
56
85
|
if (Array.isArray(raw)) {
|
|
@@ -165,24 +194,69 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
165
194
|
// not have `agent_management_id` recorded — refresh it whenever we resolve
|
|
166
195
|
// one here, regardless of whether the user passed --version.
|
|
167
196
|
let resolvedAgentManagementId;
|
|
168
|
-
//
|
|
197
|
+
// Captured AgentInfo from `findAgentMgmtId` — re-used by
|
|
198
|
+
// `detectAgentBundleMode` later so we don't pay a second
|
|
199
|
+
// `GET /v1/agents/{id}` round-trip just to read `bundle_mode`.
|
|
200
|
+
// `listAgents` returns full AgentInfo rows including the
|
|
201
|
+
// `bundle_mode` field, so the dispatch decision is already
|
|
202
|
+
// available from this resolver. Saves one network call per
|
|
203
|
+
// `aui pull` run and avoids the duplicate-fetch pattern.
|
|
204
|
+
let resolvedAgentInfo;
|
|
205
|
+
// Resolve the agent-management entry for this project.
|
|
206
|
+
//
|
|
207
|
+
// Priority order (top → first match wins):
|
|
208
|
+
//
|
|
209
|
+
// 1. `.auirc.agent_management_id` — the CANONICAL id `aui
|
|
210
|
+
// import-agent` persisted on the last import. Picks the
|
|
211
|
+
// RIGHT agent when multiple records share a `network_id`
|
|
212
|
+
// (e.g. a bundle-mode + records-mode sibling pair). Lookup
|
|
213
|
+
// is a single `getAgent(...)` — no list scan, no
|
|
214
|
+
// pagination quirks. Aligns with `push.tsx:lookupAgent…`
|
|
215
|
+
// and `version-snapshot.tsx:resolveAgentForSnapshot`.
|
|
216
|
+
//
|
|
217
|
+
// 2. `listAgents(network_id=...)` first match — back-compat
|
|
218
|
+
// path for legacy projects without `agent_management_id`
|
|
219
|
+
// in `.auirc` (imported pre-PR #54). Has the sibling-pick
|
|
220
|
+
// risk; a one-time `aui import-agent` re-import promotes
|
|
221
|
+
// the project to branch 1 on every subsequent call.
|
|
169
222
|
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// agents and pagination quirks miss the match) and was the cause of
|
|
175
|
-
// spurious "this agent has no version management" errors.
|
|
223
|
+
// 3. Session `agent_management_id` (if it points at this
|
|
224
|
+
// network) — last-resort for the rare case where the
|
|
225
|
+
// filtered list returns nothing but the session knows the
|
|
226
|
+
// id from a previous resolve.
|
|
176
227
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
228
|
+
// Each branch caches the resolved AgentInfo into
|
|
229
|
+
// `resolvedAgentInfo` so `detectAgentBundleMode` reuses it
|
|
230
|
+
// and we avoid a duplicate `GET /agents/{id}` round-trip.
|
|
180
231
|
const findAgentMgmtId = async () => {
|
|
232
|
+
// 1. Canonical: `.auirc.agent_management_id`
|
|
233
|
+
if (projectConfig.agent_management_id) {
|
|
234
|
+
try {
|
|
235
|
+
const agent = await client.agentManagement.getAgent(projectConfig.agent_management_id);
|
|
236
|
+
resolvedAgentManagementId = agent.id;
|
|
237
|
+
resolvedAgentInfo = agent;
|
|
238
|
+
if (!options.version && agent.active_version_id) {
|
|
239
|
+
activeVersionId = agent.active_version_id;
|
|
240
|
+
}
|
|
241
|
+
return agent.id;
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
// Stale id (agent deleted, scope flipped). Fall through
|
|
245
|
+
// to the network-id resolver below. Debug-log only —
|
|
246
|
+
// silent fall-through matches push.tsx's pattern for the
|
|
247
|
+
// same case.
|
|
248
|
+
if (process.env.AUI_DEBUG) {
|
|
249
|
+
console.log(`[debug] pull.findAgentMgmtId: getAgent(${projectConfig.agent_management_id}) from .auirc failed, falling back to network_id lookup: ${err instanceof Error ? err.message : err}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// 2. Network-id listing (legacy projects + general fallback)
|
|
181
254
|
try {
|
|
182
255
|
const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: networkId });
|
|
183
256
|
const agentMatch = resp.items.find((a) => a.scope.network_id === networkId || a.id === networkId);
|
|
184
257
|
if (agentMatch) {
|
|
185
258
|
resolvedAgentManagementId = agentMatch.id;
|
|
259
|
+
resolvedAgentInfo = agentMatch;
|
|
186
260
|
if (!options.version && agentMatch.active_version_id) {
|
|
187
261
|
activeVersionId = agentMatch.active_version_id;
|
|
188
262
|
}
|
|
@@ -192,10 +266,12 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
192
266
|
catch {
|
|
193
267
|
// Filtered listing failed — fall through to session hint.
|
|
194
268
|
}
|
|
269
|
+
// 3. Session hint
|
|
195
270
|
if (session.agent_management_id && session.network_id === networkId) {
|
|
196
271
|
try {
|
|
197
272
|
const agent = await client.agentManagement.getAgent(session.agent_management_id);
|
|
198
273
|
resolvedAgentManagementId = agent.id;
|
|
274
|
+
resolvedAgentInfo = agent;
|
|
199
275
|
if (!options.version && agent.active_version_id) {
|
|
200
276
|
activeVersionId = agent.active_version_id;
|
|
201
277
|
}
|
|
@@ -236,6 +312,11 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
236
312
|
}
|
|
237
313
|
else {
|
|
238
314
|
try {
|
|
315
|
+
// `findAgentMgmtId` already prefers `.auirc.agent_management_id`
|
|
316
|
+
// (its branch 1), so we get the right agent without an extra
|
|
317
|
+
// sibling-disambiguation step here. No-op when the project is
|
|
318
|
+
// legacy (no canonical id) — the function still falls through
|
|
319
|
+
// to the network-id listing.
|
|
239
320
|
const agentMgmtId = await findAgentMgmtId();
|
|
240
321
|
if (agentMgmtId && activeVersionId) {
|
|
241
322
|
try {
|
|
@@ -247,6 +328,45 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
247
328
|
}
|
|
248
329
|
log(_jsx(StatusLine, { kind: "info", label: `Using active version: ${activeVersionLabel || activeVersionId.slice(0, 10) + "…"}` }));
|
|
249
330
|
}
|
|
331
|
+
else if (agentMgmtId && projectConfig.version_id) {
|
|
332
|
+
// ─── Fallback: `.auirc.version_id` from the last import/pull
|
|
333
|
+
//
|
|
334
|
+
// `aui import-agent` persists `version_id` on every import
|
|
335
|
+
// (regular OR draft-only agents). Without this fallback, a
|
|
336
|
+
// bare `aui pull` inside a freshly-imported draft-only agent
|
|
337
|
+
// (the common case — most agents under active development
|
|
338
|
+
// have no published/activated version) would error out with
|
|
339
|
+
// "No version_id available to pull" even though the project
|
|
340
|
+
// clearly knows which version it's tracking.
|
|
341
|
+
//
|
|
342
|
+
// Hierarchy:
|
|
343
|
+
// 1. --version <id> (explicit; handled above)
|
|
344
|
+
// 2. agent.active_version_id (published+activated; the
|
|
345
|
+
// default "what's live" the
|
|
346
|
+
// user usually wants)
|
|
347
|
+
// 3. .auirc.version_id (this branch — what they
|
|
348
|
+
// originally imported / last
|
|
349
|
+
// pulled from)
|
|
350
|
+
//
|
|
351
|
+
// We still resolve the version label by calling getVersion so
|
|
352
|
+
// the UX line below matches the active-version branch above.
|
|
353
|
+
try {
|
|
354
|
+
const ver = await client.agentManagement.getVersion(agentMgmtId, projectConfig.version_id);
|
|
355
|
+
activeVersionId = ver.id;
|
|
356
|
+
activeVersionLabel = `v${ver.version_number}`;
|
|
357
|
+
log(_jsx(StatusLine, { kind: "info", label: `Using project version (from .auirc): ${activeVersionLabel}` }));
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
// The version in .auirc no longer exists on the server
|
|
361
|
+
// (got deleted, agent_id drift, etc.). Don't silently
|
|
362
|
+
// promote `projectConfig.version_id` as the activeVersionId
|
|
363
|
+
// — that would round-trip back as a 404 on /pull. Let the
|
|
364
|
+
// `!activeVersionId` guard below fire its actionable error.
|
|
365
|
+
if (process.env.AUI_DEBUG) {
|
|
366
|
+
console.log(`[debug] pull: getVersion(${projectConfig.version_id}) from .auirc failed: ${err instanceof Error ? err.message : err}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
250
370
|
}
|
|
251
371
|
catch {
|
|
252
372
|
// No version management available, pull latest
|
|
@@ -270,51 +390,297 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
270
390
|
return;
|
|
271
391
|
}
|
|
272
392
|
}
|
|
273
|
-
|
|
393
|
+
// ─── Resolve the agent-management UUID for the new pull endpoint ───
|
|
394
|
+
// The new bundle endpoint is keyed on `{agent_id, version_id}` where
|
|
395
|
+
// `agent_id` is the agent-management UUID — NOT the network_id we have
|
|
396
|
+
// in `.auirc`. Re-use the same `findAgentMgmtId` resolver as version
|
|
397
|
+
// discovery: filtered listAgents → session hint → give up loud.
|
|
398
|
+
const pullAgentMgmtId = projectConfig.agent_management_id ||
|
|
399
|
+
resolvedAgentManagementId ||
|
|
400
|
+
(await findAgentMgmtId());
|
|
401
|
+
if (!pullAgentMgmtId) {
|
|
402
|
+
throw new ConfigError("Could not resolve agent-management id for this project.", {
|
|
403
|
+
suggestion: "Re-run `aui import-agent` to back-fill `.auirc.agent_management_id`, then try `aui pull` again.",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (!activeVersionId) {
|
|
407
|
+
throw new ConfigError("No version_id available to pull — the new pull endpoint requires one.", {
|
|
408
|
+
suggestion: "Pass `--version <version-id>` explicitly, or re-run `aui import-agent` " +
|
|
409
|
+
"to refresh `.auirc.version_id`. If the project's `.auirc` already " +
|
|
410
|
+
"had a `version_id`, the server returned 404 for that id (the " +
|
|
411
|
+
"version may have been deleted or the agent_id changed) — verify " +
|
|
412
|
+
"with `aui agent --versions`.",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// ─── Mode dispatch: blob (/pull) vs records (legacy /view) ───
|
|
416
|
+
// Same dispatch as `push.tsx`. Reads `Agent.bundle_mode` on the
|
|
417
|
+
// resolved agent record and decides which API surface to use:
|
|
418
|
+
//
|
|
419
|
+
// - `bundle_mode=true` → call the new `/pull` endpoint below
|
|
420
|
+
// and write the assembled bundle inline.
|
|
421
|
+
// - `bundle_mode=false` → call legacy `client.exportAgent(...)`
|
|
422
|
+
// - `bundle_mode` missing (older backend; per-domain export +
|
|
423
|
+
// local bundle reconstruction).
|
|
424
|
+
//
|
|
425
|
+
// The two surfaces are mutually exclusive: a `/pull` request against
|
|
426
|
+
// a records-mode agent returns 422 mode-mismatch, and a legacy
|
|
427
|
+
// /view export against a blob-mode agent returns empty arrays for
|
|
428
|
+
// every domain (silently wrong — the worst kind of mismatch). The
|
|
429
|
+
// up-front check guarantees we go to the right surface on the
|
|
430
|
+
// first try.
|
|
431
|
+
// Pass the AgentInfo from `findAgentMgmtId` so the dispatcher
|
|
432
|
+
// doesn't re-fetch what we already have. `resolvedAgentInfo` is
|
|
433
|
+
// only populated when `findAgentMgmtId` ran (the common case);
|
|
434
|
+
// if `.auirc.agent_management_id` short-circuited the resolver,
|
|
435
|
+
// `cachedInfo` will be undefined and the dispatcher fetches it
|
|
436
|
+
// itself — same safe fallback as before, just one fewer call in
|
|
437
|
+
// the happy path.
|
|
438
|
+
const pullModeResolution = await detectAgentBundleMode(client, pullAgentMgmtId, resolvedAgentInfo);
|
|
439
|
+
parentSpan.setAttribute("pull.dispatch.mode", pullModeResolution.mode);
|
|
440
|
+
parentSpan.setAttribute("pull.dispatch.mode_source", pullModeResolution.source);
|
|
441
|
+
const useLegacyExport = pullModeResolution.mode === "records";
|
|
442
|
+
const spinner = startSpinner(useLegacyExport
|
|
443
|
+
? "Fetching agent data (legacy /view endpoints)..."
|
|
444
|
+
: options.tag
|
|
445
|
+
? `Pulling bundle at tag ${options.tag} (version ${activeVersionLabel || activeVersionId.slice(0, 10) + "…"})...`
|
|
446
|
+
: `Pulling bundle ${activeVersionLabel || activeVersionId.slice(0, 10) + "…"}...`);
|
|
274
447
|
try {
|
|
448
|
+
// ─── New blob endpoint OR legacy /view, decided by mode above ───
|
|
449
|
+
// `useLegacyExport` was set just before the spinner. We skip the
|
|
450
|
+
// /pull call entirely for records-mode agents — calling it would
|
|
451
|
+
// return 422 mode-mismatch and cost a useless round-trip.
|
|
452
|
+
let pullData = null;
|
|
453
|
+
// `useLegacyExport` is `let`-mutable because a server-side mode
|
|
454
|
+
// flip mid-pull (rare: admin toggles `bundle_mode` while we're
|
|
455
|
+
// dispatching) can force us to switch surfaces after the initial
|
|
456
|
+
// detection. We catch the 422 mode-mismatch below and convert it
|
|
457
|
+
// into a legacy-path fall-through.
|
|
458
|
+
let useLegacyExportLocal = useLegacyExport;
|
|
459
|
+
try {
|
|
460
|
+
if (!useLegacyExportLocal) {
|
|
461
|
+
pullData = await client.agentManagement.pullVersionBlobs(pullAgentMgmtId, activeVersionId, {
|
|
462
|
+
// Forward `--tag` so the user can pull a specific historical
|
|
463
|
+
// revision (e.g. `v3.2`). When omitted, the server picks the
|
|
464
|
+
// version row's current revision.
|
|
465
|
+
versionTag: options.tag,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
if (isModeMismatchError(err)) {
|
|
471
|
+
// Agent's mode changed since our detectAgentBundleMode call —
|
|
472
|
+
// server says it's records-mode now. Switch surfaces and
|
|
473
|
+
// continue. Single retry, no recursion.
|
|
474
|
+
parentSpan.addEvent("pull.mode_mismatch_falling_back_to_records", {
|
|
475
|
+
agent_management_id: pullAgentMgmtId,
|
|
476
|
+
version_id: activeVersionId,
|
|
477
|
+
});
|
|
478
|
+
useLegacyExportLocal = true;
|
|
479
|
+
}
|
|
480
|
+
else if (err instanceof AUIAPIError && err.status === 404) {
|
|
481
|
+
// Expected when the agent has never been Pushed via the new
|
|
482
|
+
// endpoint. Falls through to the "no blob revision" error
|
|
483
|
+
// (or the records-mode legacy export, if applicable).
|
|
484
|
+
if (process.env.AUI_DEBUG) {
|
|
485
|
+
console.log("[debug] new pull returned 404 — no blob revision for this version");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (err instanceof AUIAPIError &&
|
|
489
|
+
(err.status === 401 || err.status === 403) &&
|
|
490
|
+
!loadAgentSettingsApiKey()) {
|
|
491
|
+
spinner.warn("Authentication failed.");
|
|
492
|
+
log(_jsx(StatusLine, { kind: "warning", label: "Your access token may not have permission for the agent-settings pull endpoint." }));
|
|
493
|
+
log(_jsx(Hint, { message: "You can provide an API key as a fallback. It will be saved to ~/.aui/agent-settings-key" }));
|
|
494
|
+
const { key } = await inquirer.prompt([
|
|
495
|
+
{
|
|
496
|
+
type: "password",
|
|
497
|
+
name: "key",
|
|
498
|
+
message: "Paste the Agent Settings API key (or press Enter to abort):",
|
|
499
|
+
mask: "*",
|
|
500
|
+
},
|
|
501
|
+
]);
|
|
502
|
+
if (key && key.trim()) {
|
|
503
|
+
saveAgentSettingsApiKey(key.trim());
|
|
504
|
+
client.setAgentSettingsApiKey(key.trim());
|
|
505
|
+
log(_jsx(StatusLine, { kind: "success", label: "Key saved. Retrying..." }));
|
|
506
|
+
const retrySpinner = startSpinner("Retrying pull with API key...");
|
|
507
|
+
try {
|
|
508
|
+
pullData = await client.agentManagement.pullVersionBlobs(pullAgentMgmtId, activeVersionId, { versionTag: options.tag });
|
|
509
|
+
retrySpinner.stop();
|
|
510
|
+
}
|
|
511
|
+
catch (retryErr) {
|
|
512
|
+
retrySpinner.stop();
|
|
513
|
+
if (retryErr instanceof AUIAPIError &&
|
|
514
|
+
retryErr.status === 404) {
|
|
515
|
+
// Same strict-mode error below.
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
throw retryErr;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
throw err;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
throw err;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// ─── Shared output variables (set by either branch) ───
|
|
275
531
|
let generalSettings = {
|
|
276
532
|
general_settings: { name: projectConfig.agent_code },
|
|
277
533
|
};
|
|
278
|
-
let
|
|
279
|
-
let
|
|
280
|
-
let
|
|
281
|
-
let
|
|
282
|
-
let
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
534
|
+
let paramCount = 0;
|
|
535
|
+
let entityCount = 0;
|
|
536
|
+
let integrationCount = 0;
|
|
537
|
+
let toolCount = 0;
|
|
538
|
+
let ruleCount = 0;
|
|
539
|
+
const writtenFiles = [];
|
|
540
|
+
// Revision tag from the response (e.g. "v8.14") — saved to
|
|
541
|
+
// `.auirc.version_tag` after the file write so the next `aui pull`
|
|
542
|
+
// can default to the same tag. NOT the same as `activeVersionLabel`
|
|
543
|
+
// (which stays as the major-only "v8" for telemetry / UX).
|
|
544
|
+
let resolvedVersionTag;
|
|
545
|
+
if (pullData) {
|
|
546
|
+
// ─── New bundle path: write files inline ───
|
|
547
|
+
if (pullData.version_tag) {
|
|
548
|
+
resolvedVersionTag = pullData.version_tag;
|
|
549
|
+
parentSpan.setAttribute("pull.version_tag", pullData.version_tag);
|
|
550
|
+
}
|
|
551
|
+
parentSpan.setAttribute("pull.path", "blob_endpoint");
|
|
552
|
+
const disassembled = disassembleBundleToFiles(pullData.bundle);
|
|
553
|
+
spinner.succeed(`Pulled bundle via /pull (${disassembled.length} file(s)) at ${resolvedVersionTag || activeVersionLabel || "current"}`);
|
|
554
|
+
// ─── Empty-domain warning banner ─────────────────────────────────
|
|
555
|
+
// Same rationale as `import-agent.tsx`: an empty domain in the
|
|
556
|
+
// pulled bundle is a real server-side state (not a CLI bug), but
|
|
557
|
+
// it's usually a sign the user pulled the wrong version. Surface
|
|
558
|
+
// every empty domain prominently so they can decide whether to
|
|
559
|
+
// re-pull a different version instead of being surprised by an
|
|
560
|
+
// empty `tools/` (or empty `entities`, etc.) on disk.
|
|
561
|
+
const emptyDomains = [];
|
|
562
|
+
if (!pullData.bundle.general_settings)
|
|
563
|
+
emptyDomains.push("general_settings");
|
|
564
|
+
if (!pullData.bundle.parameters || pullData.bundle.parameters.length === 0)
|
|
565
|
+
emptyDomains.push("parameters");
|
|
566
|
+
if (!pullData.bundle.entities || pullData.bundle.entities.length === 0)
|
|
567
|
+
emptyDomains.push("entities");
|
|
568
|
+
if (!pullData.bundle.integrations || pullData.bundle.integrations.length === 0)
|
|
569
|
+
emptyDomains.push("integrations");
|
|
570
|
+
if (!pullData.bundle.rules || pullData.bundle.rules.length === 0)
|
|
571
|
+
emptyDomains.push("rules");
|
|
572
|
+
// Tools live under `agent_tools` on the live `/pull` response and
|
|
573
|
+
// under `tools` per the OpenAPI schema. Either counts as "present".
|
|
574
|
+
const bundleTools = readBundleTools(pullData.bundle);
|
|
575
|
+
if (!bundleTools || bundleTools.length === 0)
|
|
576
|
+
emptyDomains.push("tools");
|
|
577
|
+
if (emptyDomains.length > 0) {
|
|
578
|
+
log(_jsxs(Box, { flexDirection: "column", paddingX: 1, marginY: 1, children: [_jsx(StatusLine, { kind: "warning", label: `Bundle at ${resolvedVersionTag || activeVersionLabel || "current"} has 0 ${emptyDomains.join(", 0 ")}.` }), _jsx(Hint, { message: "If you expected content in these domains, you may be on the wrong version. " +
|
|
579
|
+
"Check the LIVE version in the Playground or run `aui agent --versions` to see all versions on this agent, then re-pull with `--version <id>`." })] }));
|
|
580
|
+
parentSpan.setAttribute("pull.empty_domains", emptyDomains.join(","));
|
|
581
|
+
}
|
|
582
|
+
cleanAgentFiles(projectRoot);
|
|
583
|
+
const toolsDir = path.join(projectRoot, "tools");
|
|
584
|
+
if (!fs.existsSync(toolsDir)) {
|
|
585
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
586
|
+
}
|
|
587
|
+
for (const entry of disassembled) {
|
|
588
|
+
const targetPath = path.join(projectRoot, entry.relativePath);
|
|
589
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
590
|
+
// Normalize before write — disassemble already produces the
|
|
591
|
+
// canonical shape, but normalize handles any edge cases the
|
|
592
|
+
// server might have stored historically (legacy
|
|
593
|
+
// wrap/unwrap drift, alternate domain-key locations) so the
|
|
594
|
+
// on-disk file always matches the spec.
|
|
595
|
+
const normalized = normalizeBundleFileBody(path.basename(entry.relativePath), entry.body);
|
|
596
|
+
writeJson(targetPath, normalized);
|
|
597
|
+
writtenFiles.push(entry.relativePath);
|
|
598
|
+
const basename = path.basename(entry.relativePath);
|
|
599
|
+
if (basename === "agent.aui.json") {
|
|
600
|
+
generalSettings = normalized;
|
|
601
|
+
}
|
|
602
|
+
else if (basename === "parameters.aui.json") {
|
|
603
|
+
paramCount = (normalized?.parameters || []).length;
|
|
604
|
+
}
|
|
605
|
+
else if (basename === "entities.aui.json") {
|
|
606
|
+
entityCount = (normalized?.entities || []).length;
|
|
607
|
+
}
|
|
608
|
+
else if (basename === "integrations.aui.json") {
|
|
609
|
+
integrationCount =
|
|
610
|
+
(normalized?.integrations || []).length;
|
|
611
|
+
}
|
|
612
|
+
else if (basename === "rules.aui.json") {
|
|
613
|
+
ruleCount = (normalized?.rules || []).length;
|
|
614
|
+
}
|
|
615
|
+
else if (entry.relativePath.startsWith("tools/")) {
|
|
616
|
+
toolCount++;
|
|
617
|
+
}
|
|
618
|
+
else if (basename === "tools.aui.json") {
|
|
619
|
+
toolCount += (normalized?.tools || []).length;
|
|
620
|
+
}
|
|
305
621
|
}
|
|
306
622
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
623
|
+
else if (useLegacyExportLocal) {
|
|
624
|
+
// ─── Records-mode legacy export ───────────────────────────────
|
|
625
|
+
// Restored 2026-05-24 (per owner directive: "do the fallback
|
|
626
|
+
// exactly right carefully all the prev features should be
|
|
627
|
+
// available"). For agents with `bundle_mode=false` we use the
|
|
628
|
+
// per-entity /view endpoints (`parameters/view`,
|
|
629
|
+
// `scope-entities/view`, `integrations/view`, `agent-tools`,
|
|
630
|
+
// `agent-tools/rules`, `general-settings/view`) via
|
|
631
|
+
// `client.exportAgent(...)`, then reconstruct the canonical
|
|
632
|
+
// `.aui.json` layout locally. This block mirrors the legacy
|
|
633
|
+
// pull behaviour from before the blob migration — keep in
|
|
634
|
+
// sync with `import-agent.tsx`'s equivalent branch.
|
|
635
|
+
//
|
|
636
|
+
// Auth-failure handling: the legacy /view endpoints often
|
|
637
|
+
// 401/403 against an interactive session token even when the
|
|
638
|
+
// user has access via the agent-settings API key. We surface
|
|
639
|
+
// a one-time prompt for the key, save it, and retry once.
|
|
640
|
+
parentSpan.setAttribute("pull.path", "legacy_export");
|
|
641
|
+
let parameters = { parameters: [] };
|
|
642
|
+
let entities = { entities: [] };
|
|
643
|
+
let integrations = { integrations: [] };
|
|
644
|
+
let tools = { tools: [] };
|
|
645
|
+
let rules = { rules: [] };
|
|
646
|
+
let exportData = await client.exportAgent(networkId, networkCategoryId, activeVersionId, options.scopeLevel);
|
|
647
|
+
const hasAuthErrors = exportData.errors.some((e) => /\b(401|403)\b/.test(e) ||
|
|
648
|
+
/unauthorized|forbidden|not.*member/i.test(e));
|
|
649
|
+
if (hasAuthErrors && !loadAgentSettingsApiKey()) {
|
|
650
|
+
spinner.warn("Authentication failed for some endpoints.");
|
|
651
|
+
log(_jsx(StatusLine, { kind: "warning", label: "Your access token may not have permission for the agent-settings endpoints." }));
|
|
652
|
+
log(_jsx(Hint, { message: "You can provide an API key as a fallback. It will be saved to ~/.aui/agent-settings-key" }));
|
|
653
|
+
const { key } = await inquirer.prompt([
|
|
654
|
+
{
|
|
655
|
+
type: "password",
|
|
656
|
+
name: "key",
|
|
657
|
+
message: "Paste the Agent Settings API key (or press Enter to skip):",
|
|
658
|
+
mask: "*",
|
|
659
|
+
},
|
|
660
|
+
]);
|
|
661
|
+
if (key && key.trim()) {
|
|
662
|
+
saveAgentSettingsApiKey(key.trim());
|
|
663
|
+
client.setAgentSettingsApiKey(key.trim());
|
|
664
|
+
log(_jsx(StatusLine, { kind: "success", label: "Key saved. Retrying..." }));
|
|
665
|
+
const retrySpinner = startSpinner("Retrying agent configuration fetch...");
|
|
666
|
+
exportData = await client.exportAgent(networkId, networkCategoryId, activeVersionId, options.scopeLevel);
|
|
667
|
+
retrySpinner.stop();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
generalSettings =
|
|
671
|
+
normalizeGeneralSettingsLegacy(exportData.general_settings) ||
|
|
672
|
+
generalSettings;
|
|
673
|
+
parameters =
|
|
674
|
+
normalizeWrappedLegacy("parameters", exportData.parameters) ||
|
|
675
|
+
parameters;
|
|
676
|
+
entities =
|
|
677
|
+
normalizeWrappedLegacy("entities", exportData.entities) || entities;
|
|
678
|
+
integrations =
|
|
679
|
+
normalizeWrappedLegacy("integrations", exportData.integrations) ||
|
|
680
|
+
integrations;
|
|
681
|
+
tools = normalizeWrappedLegacy("tools", exportData.tools) || tools;
|
|
682
|
+
rules = normalizeWrappedLegacy("rules", exportData.rules) || rules;
|
|
683
|
+
if (exportData.errors.length > 0) {
|
|
318
684
|
parentSpan.setStatus({
|
|
319
685
|
code: SpanStatusCode.ERROR,
|
|
320
686
|
message: exportData.errors.join("; "),
|
|
@@ -322,80 +688,126 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
322
688
|
for (const e of exportData.errors) {
|
|
323
689
|
parentSpan.recordException(new Error(e));
|
|
324
690
|
}
|
|
325
|
-
parentSpan.setAttribute("pull.failed_endpoints", exportData.errors);
|
|
691
|
+
parentSpan.setAttribute("pull.failed_endpoints", exportData.errors.join(","));
|
|
692
|
+
spinner.warn(`Fetched agent configuration (${exportData.errors.length} endpoint(s) failed)`);
|
|
693
|
+
log(_jsx(ImportWarnings, { errors: exportData.errors }));
|
|
326
694
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
cleanAgentFiles(projectRoot);
|
|
335
|
-
const toolsDir = path.join(projectRoot, "tools");
|
|
336
|
-
if (!fs.existsSync(toolsDir)) {
|
|
337
|
-
fs.mkdirSync(toolsDir, { recursive: true });
|
|
338
|
-
}
|
|
339
|
-
const writtenFiles = [];
|
|
340
|
-
writeJson(path.join(projectRoot, "agent.aui.json"), generalSettings);
|
|
341
|
-
writtenFiles.push("agent.aui.json");
|
|
342
|
-
writeJson(path.join(projectRoot, "parameters.aui.json"), parameters);
|
|
343
|
-
writtenFiles.push("parameters.aui.json");
|
|
344
|
-
writeJson(path.join(projectRoot, "entities.aui.json"), entities);
|
|
345
|
-
writtenFiles.push("entities.aui.json");
|
|
346
|
-
writeJson(path.join(projectRoot, "integrations.aui.json"), integrations);
|
|
347
|
-
writtenFiles.push("integrations.aui.json");
|
|
348
|
-
writeJson(path.join(projectRoot, "rules.aui.json"), rules);
|
|
349
|
-
writtenFiles.push("rules.aui.json");
|
|
350
|
-
const toolsData = tools;
|
|
351
|
-
if (toolsData.tools &&
|
|
352
|
-
Array.isArray(toolsData.tools) &&
|
|
353
|
-
toolsData.tools.length > 0) {
|
|
354
|
-
for (const tool of toolsData.tools) {
|
|
355
|
-
const toolCode = (tool.code ||
|
|
356
|
-
tool.name ||
|
|
357
|
-
tool.tool_name ||
|
|
358
|
-
"unknown");
|
|
359
|
-
const fileName = `${toolCode.toLowerCase().replace(/\s+/g, "_")}.aui.json`;
|
|
360
|
-
writeJson(path.join(toolsDir, fileName), { tool });
|
|
361
|
-
writtenFiles.push(`tools/${fileName}`);
|
|
695
|
+
else {
|
|
696
|
+
spinner.succeed("Agent configuration fetched (all 6 endpoints OK)");
|
|
697
|
+
}
|
|
698
|
+
cleanAgentFiles(projectRoot);
|
|
699
|
+
const toolsDir = path.join(projectRoot, "tools");
|
|
700
|
+
if (!fs.existsSync(toolsDir)) {
|
|
701
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
362
702
|
}
|
|
703
|
+
writeJson(path.join(projectRoot, "agent.aui.json"), generalSettings);
|
|
704
|
+
writtenFiles.push("agent.aui.json");
|
|
705
|
+
writeJson(path.join(projectRoot, "parameters.aui.json"), parameters);
|
|
706
|
+
writtenFiles.push("parameters.aui.json");
|
|
707
|
+
writeJson(path.join(projectRoot, "entities.aui.json"), entities);
|
|
708
|
+
writtenFiles.push("entities.aui.json");
|
|
709
|
+
writeJson(path.join(projectRoot, "integrations.aui.json"), integrations);
|
|
710
|
+
writtenFiles.push("integrations.aui.json");
|
|
711
|
+
writeJson(path.join(projectRoot, "rules.aui.json"), rules);
|
|
712
|
+
writtenFiles.push("rules.aui.json");
|
|
713
|
+
// Per-tool file layout matches the blob disassembler: one
|
|
714
|
+
// `tools/<tool_code>.aui.json` per tool, wrapped under `{ tool }`.
|
|
715
|
+
// Empty tool list collapses to a single `tools.aui.json`
|
|
716
|
+
// wrapper so the rest of the toolchain doesn't choke on a
|
|
717
|
+
// missing baseline.
|
|
718
|
+
const toolsData = tools;
|
|
719
|
+
if (toolsData.tools &&
|
|
720
|
+
Array.isArray(toolsData.tools) &&
|
|
721
|
+
toolsData.tools.length > 0) {
|
|
722
|
+
for (const tool of toolsData.tools) {
|
|
723
|
+
const toolCode = (tool.code ||
|
|
724
|
+
tool.name ||
|
|
725
|
+
tool.tool_name ||
|
|
726
|
+
"unknown");
|
|
727
|
+
const fileName = `${toolCode
|
|
728
|
+
.toLowerCase()
|
|
729
|
+
.replace(/\s+/g, "_")}.aui.json`;
|
|
730
|
+
writeJson(path.join(toolsDir, fileName), { tool });
|
|
731
|
+
writtenFiles.push(`tools/${fileName}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
writeJson(path.join(projectRoot, "tools.aui.json"), tools);
|
|
736
|
+
writtenFiles.push("tools.aui.json");
|
|
737
|
+
}
|
|
738
|
+
paramCount = (parameters?.parameters || []).length;
|
|
739
|
+
entityCount = (entities?.entities || []).length;
|
|
740
|
+
integrationCount =
|
|
741
|
+
(integrations?.integrations || []).length;
|
|
742
|
+
toolCount = (toolsData.tools || []).length;
|
|
743
|
+
ruleCount = (rules?.rules || []).length;
|
|
363
744
|
}
|
|
364
745
|
else {
|
|
365
|
-
|
|
366
|
-
|
|
746
|
+
// ─── Bundle-mode but no revision found ───
|
|
747
|
+
// The agent IS in blob-mode (we just checked) and `/pull`
|
|
748
|
+
// returned 404, meaning no bundle has been uploaded for this
|
|
749
|
+
// version_id yet. Throw a clear error pointing at `aui push`
|
|
750
|
+
// — the user almost certainly meant to seed the version
|
|
751
|
+
// first. This is a different failure mode from "agent doesn't
|
|
752
|
+
// exist" (which would have been caught earlier in mode
|
|
753
|
+
// detection).
|
|
754
|
+
parentSpan.setAttribute("pull.path", "blob_endpoint_404");
|
|
755
|
+
spinner.fail("No blob revision found at this version.");
|
|
756
|
+
throw new CLIError(options.tag
|
|
757
|
+
? `No blob revision found at tag ${options.tag} for version ${activeVersionLabel || activeVersionId}.`
|
|
758
|
+
: `No blob revision found for version ${activeVersionLabel || activeVersionId}.`, {
|
|
759
|
+
suggestion: "Run `aui push` first to create the initial blob revision via the new /push endpoint, then re-run `aui pull`.",
|
|
760
|
+
});
|
|
367
761
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
762
|
+
// ─── Skip KB export for templates ─────────────────────────────
|
|
763
|
+
//
|
|
764
|
+
// Templates are NETWORK_CATEGORY-scoped and have no `network_id`
|
|
765
|
+
// / `account_id` on their scope row, so the KB endpoint
|
|
766
|
+
// (`knowledge-base-manager/v1/knowledge-bases/view/export`)
|
|
767
|
+
// rejects with 422 ("Id must be of type PydanticObjectId") on the
|
|
768
|
+
// empty account_id query param. Mirror the same skip in
|
|
769
|
+
// `import-agent.tsx` — KBs are inherently network-scoped and
|
|
770
|
+
// templates don't have them in this data model. See
|
|
771
|
+
// `import-agent.tsx` for the longer comment + reproduction
|
|
772
|
+
// trace from 2026-05-24.
|
|
773
|
+
//
|
|
774
|
+
// `resolvedAgentInfo` is populated by `findAgentMgmtId` above
|
|
775
|
+
// (branch 1 = .auirc canonical id), so `kind` is reliable for
|
|
776
|
+
// any project that ran `aui import-agent` on a recent CLI
|
|
777
|
+
// version. Falls back to `scope.type` for legacy/edge cases.
|
|
778
|
+
const isTemplateForKb = resolvedAgentInfo?.kind === "template" ||
|
|
779
|
+
resolvedAgentInfo?.scope?.type === "NETWORK_CATEGORY";
|
|
373
780
|
const kbExportPromise = options.skipKbFiles
|
|
374
781
|
? Promise.resolve({ kind: "skipped" })
|
|
375
|
-
:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
782
|
+
: isTemplateForKb
|
|
783
|
+
? Promise.resolve({
|
|
784
|
+
kind: "skipped",
|
|
785
|
+
reason: "templates are NETWORK_CATEGORY-scoped and don't have knowledge bases",
|
|
786
|
+
})
|
|
787
|
+
: (async () => {
|
|
788
|
+
try {
|
|
789
|
+
const kbClient = new KBViewClient({
|
|
790
|
+
authToken: config.authToken,
|
|
791
|
+
apiKey: loadAgentSettingsApiKey() || options.apiKey,
|
|
792
|
+
organizationId: projectConfig.organization_id || config.organizationId || "",
|
|
793
|
+
environment: config.environment || "staging",
|
|
794
|
+
});
|
|
795
|
+
const scope = buildScope({
|
|
796
|
+
networkId,
|
|
797
|
+
organizationId: projectConfig.organization_id || config.organizationId || "",
|
|
798
|
+
accountId: projectConfig.account_id || config.accountId || "",
|
|
799
|
+
});
|
|
800
|
+
const result = await exportToFolder(kbClient, scope, projectRoot, options.withKbFiles ?? false);
|
|
801
|
+
return { kind: "ok", result };
|
|
802
|
+
}
|
|
803
|
+
catch (kbError) {
|
|
804
|
+
return {
|
|
805
|
+
kind: "error",
|
|
806
|
+
error: kbError,
|
|
807
|
+
message: kbError instanceof Error ? kbError.message : String(kbError),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
})();
|
|
399
811
|
const schemaPromise = (async () => {
|
|
400
812
|
try {
|
|
401
813
|
const data = await fetchSchemas({
|
|
@@ -423,7 +835,9 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
423
835
|
parallelSpinner.stop();
|
|
424
836
|
// Knowledge-hub status
|
|
425
837
|
if (kbOutcome.kind === "skipped") {
|
|
426
|
-
log(_jsx(StatusLine, { kind: "muted", label:
|
|
838
|
+
log(_jsx(StatusLine, { kind: "muted", label: kbOutcome.reason
|
|
839
|
+
? `Skipping knowledge hub export — ${kbOutcome.reason}.`
|
|
840
|
+
: "Skipping knowledge hub export (--skip-kb-files)" }));
|
|
427
841
|
}
|
|
428
842
|
else if (kbOutcome.kind === "ok") {
|
|
429
843
|
const r = kbOutcome.result;
|
|
@@ -459,13 +873,36 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
459
873
|
else {
|
|
460
874
|
log(_jsx(StatusLine, { kind: "warning", label: "Failed to pull schemas (non-blocking)" }));
|
|
461
875
|
}
|
|
462
|
-
//
|
|
876
|
+
// KB export failure is non-blocking — same UX as `aui import-agent`.
|
|
877
|
+
//
|
|
878
|
+
// Rationale (2026-05-24): the main bundle (`agent.aui.json`,
|
|
879
|
+
// `parameters.aui.json`, `entities.aui.json`, `integrations.aui.json`,
|
|
880
|
+
// `rules.aui.json`, `tools/*.aui.json`) has already been written to
|
|
881
|
+
// disk successfully at this point — the only thing missing is the
|
|
882
|
+
// `knowledge-hubs/` folder. Killing the whole `aui pull` and
|
|
883
|
+
// discarding the .auirc / version_tag updates over a transient KB
|
|
884
|
+
// service issue (e.g. the GCS object-rate-limit on staging that
|
|
885
|
+
// surfaces as a 500 wrapping a 429) is a worse UX than the
|
|
886
|
+
// import path's "tell the user, keep going" behaviour. Both paths
|
|
887
|
+
// now treat KB export the same way:
|
|
888
|
+
//
|
|
889
|
+
// 1. Log the failure prominently with the full server message
|
|
890
|
+
// (already done above at line ~1110).
|
|
891
|
+
// 2. Show an actionable hint pointing at `aui rag --export` /
|
|
892
|
+
// retry, instead of crashing.
|
|
893
|
+
// 3. Continue with the .auirc / baseline writes below so the
|
|
894
|
+
// project's tracking state matches what was actually pulled.
|
|
895
|
+
//
|
|
896
|
+
// The KB failure is still visible in `aui curl` and in the
|
|
897
|
+
// pre-existing per-task push-logs, so debugging the underlying
|
|
898
|
+
// GCS or KB-service issue stays straightforward.
|
|
463
899
|
if (kbOutcome.kind === "error") {
|
|
464
|
-
|
|
900
|
+
log(_jsx(StatusLine, { kind: "muted", label: "Knowledge hub export failed (non-blocking). Try again with `aui rag --export` or re-run `aui pull` once the KB service recovers." }));
|
|
465
901
|
}
|
|
466
902
|
// Update .auirc with the pulled version info and any newly resolved
|
|
467
903
|
// agent-management id (back-fills it for projects that pre-date the field).
|
|
468
904
|
const shouldRewriteConfig = !!activeVersionId ||
|
|
905
|
+
!!resolvedVersionTag ||
|
|
469
906
|
(resolvedAgentManagementId &&
|
|
470
907
|
projectConfig.agent_management_id !== resolvedAgentManagementId);
|
|
471
908
|
if (shouldRewriteConfig) {
|
|
@@ -476,6 +913,10 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
476
913
|
: {}),
|
|
477
914
|
...(activeVersionId ? { version_id: activeVersionId } : {}),
|
|
478
915
|
...(activeVersionLabel ? { version_label: activeVersionLabel } : {}),
|
|
916
|
+
// Persist the revision tag from the manifest (e.g. "v8.14")
|
|
917
|
+
// so the next `aui pull` can default `?version_tag=…` to it
|
|
918
|
+
// without an extra round-trip.
|
|
919
|
+
...(resolvedVersionTag ? { version_tag: resolvedVersionTag } : {}),
|
|
479
920
|
...(options.scopeLevel ? { scope_level: options.scopeLevel } : {}),
|
|
480
921
|
}, projectRoot);
|
|
481
922
|
}
|