aui-agent-builder 0.3.103 → 0.3.105
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 +886 -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 +2575 -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 +598 -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 +1037 -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,328 @@ 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 → return undefined
|
|
398
|
+
// for pure-legacy agents (no agent-management entry at all).
|
|
399
|
+
const pullAgentMgmtId = projectConfig.agent_management_id ||
|
|
400
|
+
resolvedAgentManagementId ||
|
|
401
|
+
(await findAgentMgmtId());
|
|
402
|
+
// ─── Mode dispatch: blob (/pull) vs records (legacy /view) ───────
|
|
403
|
+
//
|
|
404
|
+
// Three layers, mirrors `commands/import-agent.tsx:doImport`:
|
|
405
|
+
//
|
|
406
|
+
// 1. No agent_management_id at all (pure legacy)
|
|
407
|
+
// → The agent exists in `networks` but not in agent-management.
|
|
408
|
+
// It predates the agent-management rollout entirely → predates
|
|
409
|
+
// `bundle_mode` → can only be records-mode. Skip the
|
|
410
|
+
// `detectAgentBundleMode` call (it'd need an
|
|
411
|
+
// agent_management_id we don't have) and force-route to
|
|
412
|
+
// records-mode dispatch. `client.exportAgent(...)` works on
|
|
413
|
+
// scope filters alone — no agent_management_id needed.
|
|
414
|
+
//
|
|
415
|
+
// Concrete repro (2026-05-26 sibling to the import fix):
|
|
416
|
+
// legacy projects whose `.auirc` only has `agent_id`
|
|
417
|
+
// (network_id) used to fail here with "Could not resolve
|
|
418
|
+
// agent-management id for this project". Now they pull via
|
|
419
|
+
// the legacy `exportAgent` path same as `aui import-agent`
|
|
420
|
+
// for the same agent.
|
|
421
|
+
//
|
|
422
|
+
// 2. Agent_management_id present → call `detectAgentBundleMode`
|
|
423
|
+
// to read the agent's `bundle_mode` flag. Routes to /pull
|
|
424
|
+
// (bundle) or per-entity /view (records) accordingly. Reuses
|
|
425
|
+
// `resolvedAgentInfo` from `findAgentMgmtId` so the
|
|
426
|
+
// dispatcher doesn't re-fetch.
|
|
427
|
+
//
|
|
428
|
+
// 3. Bundle mode + no version_id → hard error. /pull is keyed
|
|
429
|
+
// on version_id in the URL and can't fall back to scope
|
|
430
|
+
// filters. Records mode + no version_id is fine — exportAgent
|
|
431
|
+
// handles it via scope-level filters.
|
|
432
|
+
let pullModeResolution;
|
|
433
|
+
if (!pullAgentMgmtId) {
|
|
434
|
+
// (1) Pure legacy — force records mode, skip the dispatcher
|
|
435
|
+
// call entirely (it'd need an id we don't have).
|
|
436
|
+
pullModeResolution = { mode: "records", source: "no_agent_management_id" };
|
|
437
|
+
if (process.env.AUI_DEBUG) {
|
|
438
|
+
console.log(`[debug] pull: no agent_management_id for network ${networkId} — forcing records-mode dispatch (pure-legacy agent)`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
// (2) Normal path — dispatcher reads bundle_mode from the cached
|
|
443
|
+
// AgentInfo when available, no extra round-trip.
|
|
444
|
+
pullModeResolution = await detectAgentBundleMode(client, pullAgentMgmtId, resolvedAgentInfo);
|
|
445
|
+
}
|
|
446
|
+
// (3) Bundle mode requires version_id. Records mode doesn't —
|
|
447
|
+
// `exportAgent` falls back to scope filters when versionId is
|
|
448
|
+
// undefined (the legacy contract that's been in the CLI since
|
|
449
|
+
// before version management existed).
|
|
450
|
+
if (pullModeResolution.mode === "bundle" && !activeVersionId) {
|
|
451
|
+
throw new ConfigError("No version_id available to pull — the new pull endpoint requires one.", {
|
|
452
|
+
suggestion: "Pass `--version <version-id>` explicitly, or re-run `aui import-agent` " +
|
|
453
|
+
"to refresh `.auirc.version_id`. If the project's `.auirc` already " +
|
|
454
|
+
"had a `version_id`, the server returned 404 for that id (the " +
|
|
455
|
+
"version may have been deleted or the agent_id changed) — verify " +
|
|
456
|
+
"with `aui agent --versions`.",
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
parentSpan.setAttribute("pull.dispatch.mode", pullModeResolution.mode);
|
|
460
|
+
parentSpan.setAttribute("pull.dispatch.mode_source", pullModeResolution.source);
|
|
461
|
+
const useLegacyExport = pullModeResolution.mode === "records";
|
|
462
|
+
// In the bundle-mode branch `activeVersionId` is guaranteed non-undefined
|
|
463
|
+
// (the conditional throw above bails for bundle + missing version). TS
|
|
464
|
+
// can't narrow across the throw, so the `!` assertion is documented
|
|
465
|
+
// intent. In the records-mode branch the value is unused — the legacy
|
|
466
|
+
// "Fetching agent data (legacy /view endpoints)..." string doesn't
|
|
467
|
+
// reference it.
|
|
468
|
+
const spinner = startSpinner(useLegacyExport
|
|
469
|
+
? "Fetching agent data (legacy /view endpoints)..."
|
|
470
|
+
: options.tag
|
|
471
|
+
? `Pulling bundle at tag ${options.tag} (version ${activeVersionLabel || activeVersionId.slice(0, 10) + "…"})...`
|
|
472
|
+
: `Pulling bundle ${activeVersionLabel || activeVersionId.slice(0, 10) + "…"}...`);
|
|
274
473
|
try {
|
|
474
|
+
// ─── New blob endpoint OR legacy /view, decided by mode above ───
|
|
475
|
+
// `useLegacyExport` was set just before the spinner. We skip the
|
|
476
|
+
// /pull call entirely for records-mode agents — calling it would
|
|
477
|
+
// return 422 mode-mismatch and cost a useless round-trip.
|
|
478
|
+
let pullData = null;
|
|
479
|
+
// `useLegacyExport` is `let`-mutable because a server-side mode
|
|
480
|
+
// flip mid-pull (rare: admin toggles `bundle_mode` while we're
|
|
481
|
+
// dispatching) can force us to switch surfaces after the initial
|
|
482
|
+
// detection. We catch the 422 mode-mismatch below and convert it
|
|
483
|
+
// into a legacy-path fall-through.
|
|
484
|
+
let useLegacyExportLocal = useLegacyExport;
|
|
485
|
+
try {
|
|
486
|
+
if (!useLegacyExportLocal) {
|
|
487
|
+
// Bundle-mode path: `pullAgentMgmtId` and `activeVersionId`
|
|
488
|
+
// are both guaranteed non-undefined here (the conditional
|
|
489
|
+
// throws above bail when mode is bundle and either is
|
|
490
|
+
// missing). `!` assertions are documented intent — TS can't
|
|
491
|
+
// narrow across conditional throws.
|
|
492
|
+
pullData = await client.agentManagement.pullVersionBlobs(pullAgentMgmtId, activeVersionId, {
|
|
493
|
+
// Forward `--tag` so the user can pull a specific historical
|
|
494
|
+
// revision (e.g. `v3.2`). When omitted, the server picks the
|
|
495
|
+
// version row's current revision.
|
|
496
|
+
versionTag: options.tag,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
if (isModeMismatchError(err)) {
|
|
502
|
+
// Agent's mode changed since our detectAgentBundleMode call —
|
|
503
|
+
// server says it's records-mode now. Switch surfaces and
|
|
504
|
+
// continue. Single retry, no recursion.
|
|
505
|
+
parentSpan.addEvent("pull.mode_mismatch_falling_back_to_records", {
|
|
506
|
+
agent_management_id: pullAgentMgmtId ?? "(none)",
|
|
507
|
+
version_id: activeVersionId ?? "(none)",
|
|
508
|
+
});
|
|
509
|
+
useLegacyExportLocal = true;
|
|
510
|
+
}
|
|
511
|
+
else if (err instanceof AUIAPIError && err.status === 404) {
|
|
512
|
+
// Expected when the agent has never been Pushed via the new
|
|
513
|
+
// endpoint. Falls through to the "no blob revision" error
|
|
514
|
+
// (or the records-mode legacy export, if applicable).
|
|
515
|
+
if (process.env.AUI_DEBUG) {
|
|
516
|
+
console.log("[debug] new pull returned 404 — no blob revision for this version");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (err instanceof AUIAPIError &&
|
|
520
|
+
(err.status === 401 || err.status === 403) &&
|
|
521
|
+
!loadAgentSettingsApiKey()) {
|
|
522
|
+
spinner.warn("Authentication failed.");
|
|
523
|
+
log(_jsx(StatusLine, { kind: "warning", label: "Your access token may not have permission for the agent-settings pull endpoint." }));
|
|
524
|
+
log(_jsx(Hint, { message: "You can provide an API key as a fallback. It will be saved to ~/.aui/agent-settings-key" }));
|
|
525
|
+
const { key } = await inquirer.prompt([
|
|
526
|
+
{
|
|
527
|
+
type: "password",
|
|
528
|
+
name: "key",
|
|
529
|
+
message: "Paste the Agent Settings API key (or press Enter to abort):",
|
|
530
|
+
mask: "*",
|
|
531
|
+
},
|
|
532
|
+
]);
|
|
533
|
+
if (key && key.trim()) {
|
|
534
|
+
saveAgentSettingsApiKey(key.trim());
|
|
535
|
+
client.setAgentSettingsApiKey(key.trim());
|
|
536
|
+
log(_jsx(StatusLine, { kind: "success", label: "Key saved. Retrying..." }));
|
|
537
|
+
const retrySpinner = startSpinner("Retrying pull with API key...");
|
|
538
|
+
try {
|
|
539
|
+
pullData = await client.agentManagement.pullVersionBlobs(pullAgentMgmtId, activeVersionId, { versionTag: options.tag });
|
|
540
|
+
retrySpinner.stop();
|
|
541
|
+
}
|
|
542
|
+
catch (retryErr) {
|
|
543
|
+
retrySpinner.stop();
|
|
544
|
+
if (retryErr instanceof AUIAPIError &&
|
|
545
|
+
retryErr.status === 404) {
|
|
546
|
+
// Same strict-mode error below.
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
throw retryErr;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
throw err;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
throw err;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// ─── Shared output variables (set by either branch) ───
|
|
275
562
|
let generalSettings = {
|
|
276
563
|
general_settings: { name: projectConfig.agent_code },
|
|
277
564
|
};
|
|
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
|
-
|
|
565
|
+
let paramCount = 0;
|
|
566
|
+
let entityCount = 0;
|
|
567
|
+
let integrationCount = 0;
|
|
568
|
+
let toolCount = 0;
|
|
569
|
+
let ruleCount = 0;
|
|
570
|
+
const writtenFiles = [];
|
|
571
|
+
// Revision tag from the response (e.g. "v8.14") — saved to
|
|
572
|
+
// `.auirc.version_tag` after the file write so the next `aui pull`
|
|
573
|
+
// can default to the same tag. NOT the same as `activeVersionLabel`
|
|
574
|
+
// (which stays as the major-only "v8" for telemetry / UX).
|
|
575
|
+
let resolvedVersionTag;
|
|
576
|
+
if (pullData) {
|
|
577
|
+
// ─── New bundle path: write files inline ───
|
|
578
|
+
if (pullData.version_tag) {
|
|
579
|
+
resolvedVersionTag = pullData.version_tag;
|
|
580
|
+
parentSpan.setAttribute("pull.version_tag", pullData.version_tag);
|
|
581
|
+
}
|
|
582
|
+
parentSpan.setAttribute("pull.path", "blob_endpoint");
|
|
583
|
+
const disassembled = disassembleBundleToFiles(pullData.bundle);
|
|
584
|
+
spinner.succeed(`Pulled bundle via /pull (${disassembled.length} file(s)) at ${resolvedVersionTag || activeVersionLabel || "current"}`);
|
|
585
|
+
// ─── Empty-domain warning banner ─────────────────────────────────
|
|
586
|
+
// Same rationale as `import-agent.tsx`: an empty domain in the
|
|
587
|
+
// pulled bundle is a real server-side state (not a CLI bug), but
|
|
588
|
+
// it's usually a sign the user pulled the wrong version. Surface
|
|
589
|
+
// every empty domain prominently so they can decide whether to
|
|
590
|
+
// re-pull a different version instead of being surprised by an
|
|
591
|
+
// empty `tools/` (or empty `entities`, etc.) on disk.
|
|
592
|
+
const emptyDomains = [];
|
|
593
|
+
if (!pullData.bundle.general_settings)
|
|
594
|
+
emptyDomains.push("general_settings");
|
|
595
|
+
if (!pullData.bundle.parameters || pullData.bundle.parameters.length === 0)
|
|
596
|
+
emptyDomains.push("parameters");
|
|
597
|
+
if (!pullData.bundle.entities || pullData.bundle.entities.length === 0)
|
|
598
|
+
emptyDomains.push("entities");
|
|
599
|
+
if (!pullData.bundle.integrations || pullData.bundle.integrations.length === 0)
|
|
600
|
+
emptyDomains.push("integrations");
|
|
601
|
+
if (!pullData.bundle.rules || pullData.bundle.rules.length === 0)
|
|
602
|
+
emptyDomains.push("rules");
|
|
603
|
+
// Tools live under `agent_tools` on the live `/pull` response and
|
|
604
|
+
// under `tools` per the OpenAPI schema. Either counts as "present".
|
|
605
|
+
const bundleTools = readBundleTools(pullData.bundle);
|
|
606
|
+
if (!bundleTools || bundleTools.length === 0)
|
|
607
|
+
emptyDomains.push("tools");
|
|
608
|
+
if (emptyDomains.length > 0) {
|
|
609
|
+
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. " +
|
|
610
|
+
"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>`." })] }));
|
|
611
|
+
parentSpan.setAttribute("pull.empty_domains", emptyDomains.join(","));
|
|
612
|
+
}
|
|
613
|
+
cleanAgentFiles(projectRoot);
|
|
614
|
+
const toolsDir = path.join(projectRoot, "tools");
|
|
615
|
+
if (!fs.existsSync(toolsDir)) {
|
|
616
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
617
|
+
}
|
|
618
|
+
for (const entry of disassembled) {
|
|
619
|
+
const targetPath = path.join(projectRoot, entry.relativePath);
|
|
620
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
621
|
+
// Normalize before write — disassemble already produces the
|
|
622
|
+
// canonical shape, but normalize handles any edge cases the
|
|
623
|
+
// server might have stored historically (legacy
|
|
624
|
+
// wrap/unwrap drift, alternate domain-key locations) so the
|
|
625
|
+
// on-disk file always matches the spec.
|
|
626
|
+
const normalized = normalizeBundleFileBody(path.basename(entry.relativePath), entry.body);
|
|
627
|
+
writeJson(targetPath, normalized);
|
|
628
|
+
writtenFiles.push(entry.relativePath);
|
|
629
|
+
const basename = path.basename(entry.relativePath);
|
|
630
|
+
if (basename === "agent.aui.json") {
|
|
631
|
+
generalSettings = normalized;
|
|
632
|
+
}
|
|
633
|
+
else if (basename === "parameters.aui.json") {
|
|
634
|
+
paramCount = (normalized?.parameters || []).length;
|
|
635
|
+
}
|
|
636
|
+
else if (basename === "entities.aui.json") {
|
|
637
|
+
entityCount = (normalized?.entities || []).length;
|
|
638
|
+
}
|
|
639
|
+
else if (basename === "integrations.aui.json") {
|
|
640
|
+
integrationCount =
|
|
641
|
+
(normalized?.integrations || []).length;
|
|
642
|
+
}
|
|
643
|
+
else if (basename === "rules.aui.json") {
|
|
644
|
+
ruleCount = (normalized?.rules || []).length;
|
|
645
|
+
}
|
|
646
|
+
else if (entry.relativePath.startsWith("tools/")) {
|
|
647
|
+
toolCount++;
|
|
648
|
+
}
|
|
649
|
+
else if (basename === "tools.aui.json") {
|
|
650
|
+
toolCount += (normalized?.tools || []).length;
|
|
651
|
+
}
|
|
305
652
|
}
|
|
306
653
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
654
|
+
else if (useLegacyExportLocal) {
|
|
655
|
+
// ─── Records-mode legacy export ───────────────────────────────
|
|
656
|
+
// Restored 2026-05-24 (per owner directive: "do the fallback
|
|
657
|
+
// exactly right carefully all the prev features should be
|
|
658
|
+
// available"). For agents with `bundle_mode=false` we use the
|
|
659
|
+
// per-entity /view endpoints (`parameters/view`,
|
|
660
|
+
// `scope-entities/view`, `integrations/view`, `agent-tools`,
|
|
661
|
+
// `agent-tools/rules`, `general-settings/view`) via
|
|
662
|
+
// `client.exportAgent(...)`, then reconstruct the canonical
|
|
663
|
+
// `.aui.json` layout locally. This block mirrors the legacy
|
|
664
|
+
// pull behaviour from before the blob migration — keep in
|
|
665
|
+
// sync with `import-agent.tsx`'s equivalent branch.
|
|
666
|
+
//
|
|
667
|
+
// Auth-failure handling: the legacy /view endpoints often
|
|
668
|
+
// 401/403 against an interactive session token even when the
|
|
669
|
+
// user has access via the agent-settings API key. We surface
|
|
670
|
+
// a one-time prompt for the key, save it, and retry once.
|
|
671
|
+
parentSpan.setAttribute("pull.path", "legacy_export");
|
|
672
|
+
let parameters = { parameters: [] };
|
|
673
|
+
let entities = { entities: [] };
|
|
674
|
+
let integrations = { integrations: [] };
|
|
675
|
+
let tools = { tools: [] };
|
|
676
|
+
let rules = { rules: [] };
|
|
677
|
+
let exportData = await client.exportAgent(networkId, networkCategoryId, activeVersionId, options.scopeLevel);
|
|
678
|
+
const hasAuthErrors = exportData.errors.some((e) => /\b(401|403)\b/.test(e) ||
|
|
679
|
+
/unauthorized|forbidden|not.*member/i.test(e));
|
|
680
|
+
if (hasAuthErrors && !loadAgentSettingsApiKey()) {
|
|
681
|
+
spinner.warn("Authentication failed for some endpoints.");
|
|
682
|
+
log(_jsx(StatusLine, { kind: "warning", label: "Your access token may not have permission for the agent-settings endpoints." }));
|
|
683
|
+
log(_jsx(Hint, { message: "You can provide an API key as a fallback. It will be saved to ~/.aui/agent-settings-key" }));
|
|
684
|
+
const { key } = await inquirer.prompt([
|
|
685
|
+
{
|
|
686
|
+
type: "password",
|
|
687
|
+
name: "key",
|
|
688
|
+
message: "Paste the Agent Settings API key (or press Enter to skip):",
|
|
689
|
+
mask: "*",
|
|
690
|
+
},
|
|
691
|
+
]);
|
|
692
|
+
if (key && key.trim()) {
|
|
693
|
+
saveAgentSettingsApiKey(key.trim());
|
|
694
|
+
client.setAgentSettingsApiKey(key.trim());
|
|
695
|
+
log(_jsx(StatusLine, { kind: "success", label: "Key saved. Retrying..." }));
|
|
696
|
+
const retrySpinner = startSpinner("Retrying agent configuration fetch...");
|
|
697
|
+
exportData = await client.exportAgent(networkId, networkCategoryId, activeVersionId, options.scopeLevel);
|
|
698
|
+
retrySpinner.stop();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
generalSettings =
|
|
702
|
+
normalizeGeneralSettingsLegacy(exportData.general_settings) ||
|
|
703
|
+
generalSettings;
|
|
704
|
+
parameters =
|
|
705
|
+
normalizeWrappedLegacy("parameters", exportData.parameters) ||
|
|
706
|
+
parameters;
|
|
707
|
+
entities =
|
|
708
|
+
normalizeWrappedLegacy("entities", exportData.entities) || entities;
|
|
709
|
+
integrations =
|
|
710
|
+
normalizeWrappedLegacy("integrations", exportData.integrations) ||
|
|
711
|
+
integrations;
|
|
712
|
+
tools = normalizeWrappedLegacy("tools", exportData.tools) || tools;
|
|
713
|
+
rules = normalizeWrappedLegacy("rules", exportData.rules) || rules;
|
|
714
|
+
if (exportData.errors.length > 0) {
|
|
318
715
|
parentSpan.setStatus({
|
|
319
716
|
code: SpanStatusCode.ERROR,
|
|
320
717
|
message: exportData.errors.join("; "),
|
|
@@ -322,80 +719,126 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
322
719
|
for (const e of exportData.errors) {
|
|
323
720
|
parentSpan.recordException(new Error(e));
|
|
324
721
|
}
|
|
325
|
-
parentSpan.setAttribute("pull.failed_endpoints", exportData.errors);
|
|
722
|
+
parentSpan.setAttribute("pull.failed_endpoints", exportData.errors.join(","));
|
|
723
|
+
spinner.warn(`Fetched agent configuration (${exportData.errors.length} endpoint(s) failed)`);
|
|
724
|
+
log(_jsx(ImportWarnings, { errors: exportData.errors }));
|
|
326
725
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
spinner.succeed("Agent configuration fetched (all 6 endpoints OK)");
|
|
332
|
-
}
|
|
333
|
-
// Clean existing agent files before writing new ones
|
|
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}`);
|
|
726
|
+
else {
|
|
727
|
+
spinner.succeed("Agent configuration fetched (all 6 endpoints OK)");
|
|
362
728
|
}
|
|
729
|
+
cleanAgentFiles(projectRoot);
|
|
730
|
+
const toolsDir = path.join(projectRoot, "tools");
|
|
731
|
+
if (!fs.existsSync(toolsDir)) {
|
|
732
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
733
|
+
}
|
|
734
|
+
writeJson(path.join(projectRoot, "agent.aui.json"), generalSettings);
|
|
735
|
+
writtenFiles.push("agent.aui.json");
|
|
736
|
+
writeJson(path.join(projectRoot, "parameters.aui.json"), parameters);
|
|
737
|
+
writtenFiles.push("parameters.aui.json");
|
|
738
|
+
writeJson(path.join(projectRoot, "entities.aui.json"), entities);
|
|
739
|
+
writtenFiles.push("entities.aui.json");
|
|
740
|
+
writeJson(path.join(projectRoot, "integrations.aui.json"), integrations);
|
|
741
|
+
writtenFiles.push("integrations.aui.json");
|
|
742
|
+
writeJson(path.join(projectRoot, "rules.aui.json"), rules);
|
|
743
|
+
writtenFiles.push("rules.aui.json");
|
|
744
|
+
// Per-tool file layout matches the blob disassembler: one
|
|
745
|
+
// `tools/<tool_code>.aui.json` per tool, wrapped under `{ tool }`.
|
|
746
|
+
// Empty tool list collapses to a single `tools.aui.json`
|
|
747
|
+
// wrapper so the rest of the toolchain doesn't choke on a
|
|
748
|
+
// missing baseline.
|
|
749
|
+
const toolsData = tools;
|
|
750
|
+
if (toolsData.tools &&
|
|
751
|
+
Array.isArray(toolsData.tools) &&
|
|
752
|
+
toolsData.tools.length > 0) {
|
|
753
|
+
for (const tool of toolsData.tools) {
|
|
754
|
+
const toolCode = (tool.code ||
|
|
755
|
+
tool.name ||
|
|
756
|
+
tool.tool_name ||
|
|
757
|
+
"unknown");
|
|
758
|
+
const fileName = `${toolCode
|
|
759
|
+
.toLowerCase()
|
|
760
|
+
.replace(/\s+/g, "_")}.aui.json`;
|
|
761
|
+
writeJson(path.join(toolsDir, fileName), { tool });
|
|
762
|
+
writtenFiles.push(`tools/${fileName}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
writeJson(path.join(projectRoot, "tools.aui.json"), tools);
|
|
767
|
+
writtenFiles.push("tools.aui.json");
|
|
768
|
+
}
|
|
769
|
+
paramCount = (parameters?.parameters || []).length;
|
|
770
|
+
entityCount = (entities?.entities || []).length;
|
|
771
|
+
integrationCount =
|
|
772
|
+
(integrations?.integrations || []).length;
|
|
773
|
+
toolCount = (toolsData.tools || []).length;
|
|
774
|
+
ruleCount = (rules?.rules || []).length;
|
|
363
775
|
}
|
|
364
776
|
else {
|
|
365
|
-
|
|
366
|
-
|
|
777
|
+
// ─── Bundle-mode but no revision found ───
|
|
778
|
+
// The agent IS in blob-mode (we just checked) and `/pull`
|
|
779
|
+
// returned 404, meaning no bundle has been uploaded for this
|
|
780
|
+
// version_id yet. Throw a clear error pointing at `aui push`
|
|
781
|
+
// — the user almost certainly meant to seed the version
|
|
782
|
+
// first. This is a different failure mode from "agent doesn't
|
|
783
|
+
// exist" (which would have been caught earlier in mode
|
|
784
|
+
// detection).
|
|
785
|
+
parentSpan.setAttribute("pull.path", "blob_endpoint_404");
|
|
786
|
+
spinner.fail("No blob revision found at this version.");
|
|
787
|
+
throw new CLIError(options.tag
|
|
788
|
+
? `No blob revision found at tag ${options.tag} for version ${activeVersionLabel || activeVersionId}.`
|
|
789
|
+
: `No blob revision found for version ${activeVersionLabel || activeVersionId}.`, {
|
|
790
|
+
suggestion: "Run `aui push` first to create the initial blob revision via the new /push endpoint, then re-run `aui pull`.",
|
|
791
|
+
});
|
|
367
792
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
793
|
+
// ─── Skip KB export for templates ─────────────────────────────
|
|
794
|
+
//
|
|
795
|
+
// Templates are NETWORK_CATEGORY-scoped and have no `network_id`
|
|
796
|
+
// / `account_id` on their scope row, so the KB endpoint
|
|
797
|
+
// (`knowledge-base-manager/v1/knowledge-bases/view/export`)
|
|
798
|
+
// rejects with 422 ("Id must be of type PydanticObjectId") on the
|
|
799
|
+
// empty account_id query param. Mirror the same skip in
|
|
800
|
+
// `import-agent.tsx` — KBs are inherently network-scoped and
|
|
801
|
+
// templates don't have them in this data model. See
|
|
802
|
+
// `import-agent.tsx` for the longer comment + reproduction
|
|
803
|
+
// trace from 2026-05-24.
|
|
804
|
+
//
|
|
805
|
+
// `resolvedAgentInfo` is populated by `findAgentMgmtId` above
|
|
806
|
+
// (branch 1 = .auirc canonical id), so `kind` is reliable for
|
|
807
|
+
// any project that ran `aui import-agent` on a recent CLI
|
|
808
|
+
// version. Falls back to `scope.type` for legacy/edge cases.
|
|
809
|
+
const isTemplateForKb = resolvedAgentInfo?.kind === "template" ||
|
|
810
|
+
resolvedAgentInfo?.scope?.type === "NETWORK_CATEGORY";
|
|
373
811
|
const kbExportPromise = options.skipKbFiles
|
|
374
812
|
? 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
|
-
|
|
813
|
+
: isTemplateForKb
|
|
814
|
+
? Promise.resolve({
|
|
815
|
+
kind: "skipped",
|
|
816
|
+
reason: "templates are NETWORK_CATEGORY-scoped and don't have knowledge bases",
|
|
817
|
+
})
|
|
818
|
+
: (async () => {
|
|
819
|
+
try {
|
|
820
|
+
const kbClient = new KBViewClient({
|
|
821
|
+
authToken: config.authToken,
|
|
822
|
+
apiKey: loadAgentSettingsApiKey() || options.apiKey,
|
|
823
|
+
organizationId: projectConfig.organization_id || config.organizationId || "",
|
|
824
|
+
environment: config.environment || "staging",
|
|
825
|
+
});
|
|
826
|
+
const scope = buildScope({
|
|
827
|
+
networkId,
|
|
828
|
+
organizationId: projectConfig.organization_id || config.organizationId || "",
|
|
829
|
+
accountId: projectConfig.account_id || config.accountId || "",
|
|
830
|
+
});
|
|
831
|
+
const result = await exportToFolder(kbClient, scope, projectRoot, options.withKbFiles ?? false);
|
|
832
|
+
return { kind: "ok", result };
|
|
833
|
+
}
|
|
834
|
+
catch (kbError) {
|
|
835
|
+
return {
|
|
836
|
+
kind: "error",
|
|
837
|
+
error: kbError,
|
|
838
|
+
message: kbError instanceof Error ? kbError.message : String(kbError),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
})();
|
|
399
842
|
const schemaPromise = (async () => {
|
|
400
843
|
try {
|
|
401
844
|
const data = await fetchSchemas({
|
|
@@ -423,7 +866,9 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
423
866
|
parallelSpinner.stop();
|
|
424
867
|
// Knowledge-hub status
|
|
425
868
|
if (kbOutcome.kind === "skipped") {
|
|
426
|
-
log(_jsx(StatusLine, { kind: "muted", label:
|
|
869
|
+
log(_jsx(StatusLine, { kind: "muted", label: kbOutcome.reason
|
|
870
|
+
? `Skipping knowledge hub export — ${kbOutcome.reason}.`
|
|
871
|
+
: "Skipping knowledge hub export (--skip-kb-files)" }));
|
|
427
872
|
}
|
|
428
873
|
else if (kbOutcome.kind === "ok") {
|
|
429
874
|
const r = kbOutcome.result;
|
|
@@ -459,13 +904,36 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
459
904
|
else {
|
|
460
905
|
log(_jsx(StatusLine, { kind: "warning", label: "Failed to pull schemas (non-blocking)" }));
|
|
461
906
|
}
|
|
462
|
-
//
|
|
907
|
+
// KB export failure is non-blocking — same UX as `aui import-agent`.
|
|
908
|
+
//
|
|
909
|
+
// Rationale (2026-05-24): the main bundle (`agent.aui.json`,
|
|
910
|
+
// `parameters.aui.json`, `entities.aui.json`, `integrations.aui.json`,
|
|
911
|
+
// `rules.aui.json`, `tools/*.aui.json`) has already been written to
|
|
912
|
+
// disk successfully at this point — the only thing missing is the
|
|
913
|
+
// `knowledge-hubs/` folder. Killing the whole `aui pull` and
|
|
914
|
+
// discarding the .auirc / version_tag updates over a transient KB
|
|
915
|
+
// service issue (e.g. the GCS object-rate-limit on staging that
|
|
916
|
+
// surfaces as a 500 wrapping a 429) is a worse UX than the
|
|
917
|
+
// import path's "tell the user, keep going" behaviour. Both paths
|
|
918
|
+
// now treat KB export the same way:
|
|
919
|
+
//
|
|
920
|
+
// 1. Log the failure prominently with the full server message
|
|
921
|
+
// (already done above at line ~1110).
|
|
922
|
+
// 2. Show an actionable hint pointing at `aui rag --export` /
|
|
923
|
+
// retry, instead of crashing.
|
|
924
|
+
// 3. Continue with the .auirc / baseline writes below so the
|
|
925
|
+
// project's tracking state matches what was actually pulled.
|
|
926
|
+
//
|
|
927
|
+
// The KB failure is still visible in `aui curl` and in the
|
|
928
|
+
// pre-existing per-task push-logs, so debugging the underlying
|
|
929
|
+
// GCS or KB-service issue stays straightforward.
|
|
463
930
|
if (kbOutcome.kind === "error") {
|
|
464
|
-
|
|
931
|
+
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
932
|
}
|
|
466
933
|
// Update .auirc with the pulled version info and any newly resolved
|
|
467
934
|
// agent-management id (back-fills it for projects that pre-date the field).
|
|
468
935
|
const shouldRewriteConfig = !!activeVersionId ||
|
|
936
|
+
!!resolvedVersionTag ||
|
|
469
937
|
(resolvedAgentManagementId &&
|
|
470
938
|
projectConfig.agent_management_id !== resolvedAgentManagementId);
|
|
471
939
|
if (shouldRewriteConfig) {
|
|
@@ -476,6 +944,10 @@ async function _pullAgent(parentSpan, options = {}) {
|
|
|
476
944
|
: {}),
|
|
477
945
|
...(activeVersionId ? { version_id: activeVersionId } : {}),
|
|
478
946
|
...(activeVersionLabel ? { version_label: activeVersionLabel } : {}),
|
|
947
|
+
// Persist the revision tag from the manifest (e.g. "v8.14")
|
|
948
|
+
// so the next `aui pull` can default `?version_tag=…` to it
|
|
949
|
+
// without an extra round-trip.
|
|
950
|
+
...(resolvedVersionTag ? { version_tag: resolvedVersionTag } : {}),
|
|
479
951
|
...(options.scopeLevel ? { scope_level: options.scopeLevel } : {}),
|
|
480
952
|
}, projectRoot);
|
|
481
953
|
}
|