aui-agent-builder 0.3.77 → 0.3.79
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/push.js +187 -48
- package/dist/commands/push.js.map +1 -1
- package/dist/errors/index.d.ts +1 -12
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +19 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/index.js +36 -36
- package/dist/index.js.map +1 -1
- package/dist/ui/views/PushView.d.ts +3 -1
- package/dist/ui/views/PushView.d.ts.map +1 -1
- package/dist/ui/views/PushView.js +8 -2
- package/dist/ui/views/PushView.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/push.js
CHANGED
|
@@ -11,7 +11,7 @@ import { findAuiFiles, parseAuiFile } from "../utils/index.js";
|
|
|
11
11
|
import { validate } from "./validate.js";
|
|
12
12
|
import { getTracer, SpanStatusCode, setUserContext } from "../telemetry.js";
|
|
13
13
|
import { getItemLevelDiff } from "../utils/git.js";
|
|
14
|
-
import { AuthenticationError, ConfigError, ValidationError } from "../errors/index.js";
|
|
14
|
+
import { AuthenticationError, CLIError, ConfigError, ValidationError } from "../errors/index.js";
|
|
15
15
|
import { StatusLine, Spinner, ErrorDisplay, Hint, } from "../ui/components/index.js";
|
|
16
16
|
import { colors, icons } from "../ui/theme.js";
|
|
17
17
|
import { PushFileSummary, PushChangesView, PushTaskLine, PushFinalSummary, } from "../ui/views/PushView.js";
|
|
@@ -439,8 +439,6 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
439
439
|
log(_jsx(StatusLine, { kind: "info", label: `Scope level: ${effectiveScopeLevel} (from ${options.scopeLevel ? "flag" : ".auirc"})` }));
|
|
440
440
|
}
|
|
441
441
|
const agentSettingsParams = await resolveAgentSettingsParams(config, projectConfig, session, projectRoot, effectiveScopeLevel);
|
|
442
|
-
if (!agentSettingsParams)
|
|
443
|
-
return;
|
|
444
442
|
const client = new AUIClient({
|
|
445
443
|
baseUrl: config.apiUrl,
|
|
446
444
|
authToken: config.authToken,
|
|
@@ -466,9 +464,6 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
466
464
|
let prePushDraft = null;
|
|
467
465
|
if (projectConfig.version_id || options.versionId) {
|
|
468
466
|
prePushDraft = await resolveVersionDraft(config, projectConfig, session, options.versionId);
|
|
469
|
-
if (!prePushDraft) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
467
|
agentSettingsParams.version_id = prePushDraft.versionId;
|
|
473
468
|
log(_jsx(StatusLine, { kind: "info", label: `Pushing into draft version: ${prePushDraft.label}` }));
|
|
474
469
|
}
|
|
@@ -584,8 +579,11 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
584
579
|
}
|
|
585
580
|
return stepFailed.length === 0 && !authFailed;
|
|
586
581
|
}
|
|
587
|
-
catch {
|
|
588
|
-
|
|
582
|
+
catch (err) {
|
|
583
|
+
// Don't swallow — a thrown error here means the loop body itself
|
|
584
|
+
// (not a per-task failure) blew up. Letting the original `} catch
|
|
585
|
+
// { return false; }` swallow it caused exit 0 with no telemetry.
|
|
586
|
+
throw err;
|
|
589
587
|
}
|
|
590
588
|
};
|
|
591
589
|
const paramTasks = pushTasks.filter((t) => t.type === "put-parameters" ||
|
|
@@ -615,6 +613,22 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
615
613
|
await pushStep(rulesTasks, "Pushing rules", false);
|
|
616
614
|
// Auth fallback
|
|
617
615
|
if (authFailed && authFailedTasks.length > 0 && !savedApiKey) {
|
|
616
|
+
// The auth fallback prompts for an API key. In a non-TTY environment
|
|
617
|
+
// (`agent-builder-bff` E2B sandbox, CI, JSON mode being piped) the
|
|
618
|
+
// inquirer prompt would block forever waiting on stdin and the
|
|
619
|
+
// sandbox would eventually SIGTERM the process — exactly the silent
|
|
620
|
+
// failure mode Aviram and Dor were hitting. Detect that up front
|
|
621
|
+
// and throw a structured AuthenticationError so handleError can
|
|
622
|
+
// print it, emit the JSON envelope, and exit non-zero.
|
|
623
|
+
const isInteractive = !json &&
|
|
624
|
+
process.stdin.isTTY === true &&
|
|
625
|
+
process.stdout.isTTY === true;
|
|
626
|
+
if (!isInteractive) {
|
|
627
|
+
failed += authFailedTasks.length;
|
|
628
|
+
throw new AuthenticationError(`Authentication failed for ${authFailedTasks.length} push task(s); cannot prompt for an API key (non-interactive session).`, {
|
|
629
|
+
suggestion: "Pass --api-key <key>, set AUI_AGENT_TOOLS_API_KEY, or run `aui login` to refresh credentials.",
|
|
630
|
+
});
|
|
631
|
+
}
|
|
618
632
|
log(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(StatusLine, { kind: "warning", label: "Authentication failed. Your access token may not have permission." }), _jsx(Hint, { message: "You can provide an API key as a fallback. It will be saved to ~/.aui/agent-settings-key" })] }));
|
|
619
633
|
const { key } = await inquirer.prompt([
|
|
620
634
|
{
|
|
@@ -675,31 +689,79 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
675
689
|
// source of truth for this push. Runs regardless of entity-push outcomes
|
|
676
690
|
// so the file history is preserved even on partial DB failures.
|
|
677
691
|
// Files = source of truth; DB updates are best-effort.
|
|
692
|
+
//
|
|
693
|
+
// IMPORTANT: snapshot failures must be loud — they are NOT counted in
|
|
694
|
+
// the entity `failed` counter, so without explicit surfacing here and
|
|
695
|
+
// a thrown CLIError at the end of `_push`, a snapshot failure would
|
|
696
|
+
// be hidden behind the "All N change(s) pushed successfully" summary
|
|
697
|
+
// and the process would exit 0.
|
|
698
|
+
// Retry policy: snapshot upload is the source-of-truth step; transient
|
|
699
|
+
// failures (network blips, 5xx, timeouts) are common with multipart
|
|
700
|
+
// uploads, so we retry up to 3 times with exponential backoff (1s, 2s,
|
|
701
|
+
// 4s) before giving up. 4xx responses are still retried — keeping the
|
|
702
|
+
// policy dumb here mirrors the single retry already in the api-client
|
|
703
|
+
// layer for entity pushes.
|
|
678
704
|
let snapshotSucceeded = false;
|
|
679
705
|
let snapshotError;
|
|
706
|
+
let snapshotAttempts = 0;
|
|
680
707
|
if (prePushDraft) {
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
708
|
+
const SNAPSHOT_MAX_ATTEMPTS = 4;
|
|
709
|
+
const SNAPSHOT_RETRY_BASE_MS = 1000;
|
|
710
|
+
for (let attempt = 1; attempt <= SNAPSHOT_MAX_ATTEMPTS; attempt++) {
|
|
711
|
+
snapshotAttempts = attempt;
|
|
712
|
+
const label = attempt === 1
|
|
713
|
+
? "Pushing snapshot (file state)..."
|
|
714
|
+
: `Retrying snapshot push (attempt ${attempt}/${SNAPSHOT_MAX_ATTEMPTS})...`;
|
|
715
|
+
if (json)
|
|
716
|
+
stderrLog(label);
|
|
717
|
+
const snapshotSpinner = json ? null : startSpinner(label);
|
|
718
|
+
let attemptError;
|
|
719
|
+
try {
|
|
720
|
+
const snapshotResult = await pushSnapshot(client, prePushDraft.agentId, prePushDraft.versionId, projectRoot, fileData);
|
|
721
|
+
if (snapshotResult.success) {
|
|
722
|
+
const okMsg = attempt === 1
|
|
723
|
+
? `Snapshot pushed (${fileData.length} file(s))`
|
|
724
|
+
: `Snapshot pushed (${fileData.length} file(s), attempt ${attempt}/${SNAPSHOT_MAX_ATTEMPTS})`;
|
|
725
|
+
if (snapshotSpinner)
|
|
726
|
+
snapshotSpinner.succeed(okMsg);
|
|
727
|
+
else
|
|
728
|
+
stderrLog(okMsg);
|
|
729
|
+
snapshotSucceeded = true;
|
|
730
|
+
snapshotError = undefined;
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
attemptError = snapshotResult.error || "Unknown snapshot error";
|
|
688
734
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
snapshotError = snapshotResult.error || "Unknown snapshot error";
|
|
692
|
-
pushSpan.setAttribute("push.snapshot.success", false);
|
|
693
|
-
pushSpan.setAttribute("push.snapshot.error", snapshotError);
|
|
735
|
+
catch (error) {
|
|
736
|
+
attemptError = error instanceof Error ? error.message : String(error);
|
|
694
737
|
}
|
|
738
|
+
snapshotError = attemptError;
|
|
739
|
+
const isLast = attempt === SNAPSHOT_MAX_ATTEMPTS;
|
|
740
|
+
const failMsg = isLast
|
|
741
|
+
? `Snapshot push failed after ${attempt} attempt(s): ${attemptError}`
|
|
742
|
+
: `Snapshot push failed (attempt ${attempt}/${SNAPSHOT_MAX_ATTEMPTS}): ${attemptError}`;
|
|
743
|
+
if (snapshotSpinner)
|
|
744
|
+
snapshotSpinner.fail(failMsg);
|
|
745
|
+
else
|
|
746
|
+
stderrLog(failMsg);
|
|
747
|
+
if (isLast)
|
|
748
|
+
break;
|
|
749
|
+
const delayMs = SNAPSHOT_RETRY_BASE_MS * Math.pow(2, attempt - 1);
|
|
750
|
+
if (json)
|
|
751
|
+
stderrLog(`Waiting ${delayMs}ms before retry...`);
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
695
753
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
pushSpan.setAttribute("push.snapshot.success", false);
|
|
754
|
+
pushSpan.setAttribute("push.snapshot.success", snapshotSucceeded);
|
|
755
|
+
pushSpan.setAttribute("push.snapshot.attempts", snapshotAttempts);
|
|
756
|
+
if (!snapshotSucceeded && snapshotError) {
|
|
700
757
|
pushSpan.setAttribute("push.snapshot.error", snapshotError);
|
|
701
758
|
}
|
|
702
759
|
}
|
|
760
|
+
const snapshotStatus = prePushDraft
|
|
761
|
+
? snapshotSucceeded
|
|
762
|
+
? "succeeded"
|
|
763
|
+
: "failed"
|
|
764
|
+
: undefined;
|
|
703
765
|
const memoryPath = writePushMemory(projectRoot, agentCodeStr, agentIdStr, pushTasks, succeededFiles, pushFailures);
|
|
704
766
|
// ─── Baseline Update ───
|
|
705
767
|
// Only commit baseline if snapshot succeeded (or no draft = legacy mode).
|
|
@@ -720,7 +782,7 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
720
782
|
baselineUpdated = true;
|
|
721
783
|
}
|
|
722
784
|
}
|
|
723
|
-
log(_jsx(PushFinalSummary, { succeeded: succeeded, failed: failed, baselineUpdated: baselineUpdated, logDir: logRelPath, memoryPath: memoryPath }));
|
|
785
|
+
log(_jsx(PushFinalSummary, { succeeded: succeeded, failed: failed, baselineUpdated: baselineUpdated, logDir: logRelPath, memoryPath: memoryPath, snapshotStatus: snapshotStatus, snapshotError: snapshotError }));
|
|
724
786
|
if (failed > 0) {
|
|
725
787
|
log(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(StatusLine, { kind: "warning", label: `${failed} entity change(s) failed to push to DB.` }), pushFailures.map((f) => (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { color: "red", children: [" ", icons.error, " ", f.label] }), _jsxs(Text, { color: colors.muted, children: [" Error: ", f.error] }), f.file && _jsxs(Text, { color: colors.muted, children: [" File: ", f.file] })] }, f.label))), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.info, bold: true, children: "What to do next: " }), _jsxs(Text, { color: colors.muted, children: ["Fix the issues above and re-run ", _jsx(Text, { bold: true, children: "aui push" }), " to retry the failed changes."] })] })] }));
|
|
726
788
|
}
|
|
@@ -736,17 +798,77 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
736
798
|
log(_jsx(StatusLine, { kind: "warning", label: `Snapshot saved for draft ${prePushDraft.label}, but ${failed} DB update(s) failed. Re-run "aui push" to retry the DB updates before publishing.` }));
|
|
737
799
|
}
|
|
738
800
|
}
|
|
801
|
+
const snapshotFailed = snapshotStatus === "failed";
|
|
739
802
|
pushSpan.setAttribute("push.exit_reason", failed > 0
|
|
740
803
|
? succeeded > 0
|
|
741
804
|
? "partial_failure"
|
|
742
805
|
: "failed"
|
|
743
|
-
:
|
|
806
|
+
: snapshotFailed
|
|
807
|
+
? "snapshot_failed"
|
|
808
|
+
: "completed");
|
|
744
809
|
pushSpan.setAttribute("push.succeeded_count", succeeded);
|
|
745
810
|
pushSpan.setAttribute("push.failed_count", failed);
|
|
811
|
+
// ─── Final structured JSON envelope (success path) ───
|
|
812
|
+
// The BFF / CI consumes --json output programmatically. On success we
|
|
813
|
+
// emit a single envelope at the very end so the caller can parse one
|
|
814
|
+
// top-level JSON document with the full push outcome. Failure paths
|
|
815
|
+
// are handled by handleError() via outputJsonError().
|
|
816
|
+
if (json && !snapshotFailed && failed === 0) {
|
|
817
|
+
outputJson({
|
|
818
|
+
agent: {
|
|
819
|
+
code: agentCodeStr,
|
|
820
|
+
id: agentIdStr,
|
|
821
|
+
},
|
|
822
|
+
version: prePushDraft
|
|
823
|
+
? { id: prePushDraft.versionId, label: prePushDraft.label }
|
|
824
|
+
: undefined,
|
|
825
|
+
succeeded_count: succeeded,
|
|
826
|
+
failed_count: 0,
|
|
827
|
+
succeeded_files: succeededFiles,
|
|
828
|
+
snapshot: snapshotStatus
|
|
829
|
+
? {
|
|
830
|
+
status: snapshotStatus,
|
|
831
|
+
attempts: snapshotAttempts,
|
|
832
|
+
}
|
|
833
|
+
: undefined,
|
|
834
|
+
baseline_updated: baselineUpdated,
|
|
835
|
+
log_dir: logRelPath,
|
|
836
|
+
memory_path: memoryPath,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
// ─── Failure throws (loud + non-zero exit + JSON error envelope) ───
|
|
840
|
+
// Snapshot failure takes priority because if the snapshot doesn't land
|
|
841
|
+
// the version's file history is incomplete, even when individual DB
|
|
842
|
+
// updates succeeded. Falls through to handleError() which prints the
|
|
843
|
+
// formatted error and exits non-zero.
|
|
844
|
+
if (snapshotFailed) {
|
|
845
|
+
throw new CLIError(`Snapshot upload failed${snapshotError ? `: ${snapshotError}` : ""}`, {
|
|
846
|
+
suggestion: "Re-run `aui push` to retry the snapshot. Your local files are the source of truth — they remain unchanged.",
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
// Entity-task partial / total failure. Without this throw, `aui push`
|
|
850
|
+
// would print "X failed" to stdout and exit 0 — the BFF would have no
|
|
851
|
+
// way to know the push didn't fully apply, and Logfire would record a
|
|
852
|
+
// "completed" span. This was Aviram and Dor's reported bug.
|
|
853
|
+
if (failed > 0) {
|
|
854
|
+
const labels = pushFailures.map((f) => f.label).join(", ");
|
|
855
|
+
throw new CLIError(`${failed} entity push task(s) failed: ${labels}`, {
|
|
856
|
+
suggestion: succeeded > 0
|
|
857
|
+
? `${succeeded} change(s) were saved. Fix the errors above and re-run \`aui push\` to retry the rest.`
|
|
858
|
+
: "Fix the errors above and re-run `aui push`.",
|
|
859
|
+
});
|
|
860
|
+
}
|
|
746
861
|
}
|
|
747
862
|
catch (error) {
|
|
748
863
|
if (spinner)
|
|
749
864
|
spinner.fail("Push failed");
|
|
865
|
+
// CLIErrors carry actionable info and a meaningful exit code — let
|
|
866
|
+
// handleError() format them and exit non-zero. Without this re-throw
|
|
867
|
+
// a snapshot-failure CLIError thrown above would be swallowed in TUI
|
|
868
|
+
// mode and the process would exit 0.
|
|
869
|
+
if (error instanceof CLIError) {
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
750
872
|
if (!json)
|
|
751
873
|
log(_jsx(ErrorDisplay, { error: error }));
|
|
752
874
|
else
|
|
@@ -754,6 +876,10 @@ async function _push(pushSpan, agentCode, options = {}) {
|
|
|
754
876
|
}
|
|
755
877
|
}
|
|
756
878
|
async function resolveVersionDraft(config, projectConfig, session, explicitVersionId) {
|
|
879
|
+
// Every error path below MUST throw a typed CLIError (not return null).
|
|
880
|
+
// Returning null silently exits the CLI with code 0 — the BFF then thinks
|
|
881
|
+
// the push succeeded when nothing actually happened, and the failure
|
|
882
|
+
// never reaches Logfire because no exception bubbled to handleError.
|
|
757
883
|
const client = new AUIClient({
|
|
758
884
|
baseUrl: config.apiUrl,
|
|
759
885
|
authToken: config.authToken,
|
|
@@ -800,24 +926,29 @@ async function resolveVersionDraft(config, projectConfig, session, explicitVersi
|
|
|
800
926
|
}
|
|
801
927
|
}
|
|
802
928
|
if (!agentInfo) {
|
|
803
|
-
|
|
804
|
-
|
|
929
|
+
throw new ConfigError("Could not resolve agent for version management.", {
|
|
930
|
+
suggestion: "Run `aui import-agent` to link an agent, or check your session with `aui status`.",
|
|
931
|
+
});
|
|
805
932
|
}
|
|
806
933
|
// If user passed --version-id, validate it's a draft
|
|
807
934
|
if (explicitVersionId) {
|
|
935
|
+
let ver;
|
|
808
936
|
try {
|
|
809
|
-
|
|
810
|
-
if (ver.status !== "draft") {
|
|
811
|
-
log(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(StatusLine, { kind: "error", label: `Version v${ver.version_number} is "${ver.status}" — you can only push to a draft version.` }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.info, bold: true, children: "What to do next:" }), _jsxs(Text, { color: colors.muted, children: [" 1. Create a new draft version: ", _jsx(Text, { bold: true, children: "aui version create" })] }), _jsxs(Text, { color: colors.muted, children: [" 2. Then push with: ", _jsxs(Text, { bold: true, children: ["aui push --version-id ", '<new-draft-id>'] })] })] })] }));
|
|
812
|
-
return null;
|
|
813
|
-
}
|
|
814
|
-
const label = `v${ver.version_number}`;
|
|
815
|
-
return { versionId: ver.id, label, agentId: agentInfo.id };
|
|
937
|
+
ver = await client.agentManagement.getVersion(agentInfo.id, explicitVersionId);
|
|
816
938
|
}
|
|
817
939
|
catch (error) {
|
|
818
|
-
|
|
819
|
-
|
|
940
|
+
throw new CLIError(`Could not fetch version "${explicitVersionId}": ${error instanceof Error ? error.message : String(error)}`, {
|
|
941
|
+
suggestion: "Check the version ID with `aui version list` and try again.",
|
|
942
|
+
cause: error,
|
|
943
|
+
});
|
|
820
944
|
}
|
|
945
|
+
if (ver.status !== "draft") {
|
|
946
|
+
throw new ValidationError(`Version v${ver.version_number} is "${ver.status}" — you can only push to a draft version.`, {
|
|
947
|
+
suggestion: "Create a new draft with `aui version create`, then push with `aui push --version-id <new-draft-id>`.",
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
const label = `v${ver.version_number}`;
|
|
951
|
+
return { versionId: ver.id, label, agentId: agentInfo.id };
|
|
821
952
|
}
|
|
822
953
|
// Resolve from .auirc version_id or auto-detect drafts
|
|
823
954
|
let allVersions = [];
|
|
@@ -825,17 +956,20 @@ async function resolveVersionDraft(config, projectConfig, session, explicitVersi
|
|
|
825
956
|
const versionsResp = await client.agentManagement.listVersions(agentInfo.id, 1, 50);
|
|
826
957
|
allVersions = versionsResp.items;
|
|
827
958
|
}
|
|
828
|
-
catch {
|
|
829
|
-
|
|
830
|
-
|
|
959
|
+
catch (error) {
|
|
960
|
+
throw new CLIError("Could not fetch versions for this agent.", {
|
|
961
|
+
suggestion: "Check your connection and try again. Use `aui version list` to debug.",
|
|
962
|
+
cause: error,
|
|
963
|
+
});
|
|
831
964
|
}
|
|
832
965
|
// If .auirc has a version_id, validate it's still a draft
|
|
833
966
|
if (projectConfig.version_id) {
|
|
834
967
|
const configVersion = allVersions.find((v) => v.id === projectConfig.version_id);
|
|
835
968
|
if (configVersion) {
|
|
836
969
|
if (configVersion.status !== "draft") {
|
|
837
|
-
|
|
838
|
-
|
|
970
|
+
throw new ValidationError(`The version in your .auirc (v${configVersion.version_number}) is "${configVersion.status}" — you can only push to a draft version.`, {
|
|
971
|
+
suggestion: "Create a new draft (`aui version create`), import it (`aui import-agent --version <new-draft-id>`), then `aui push`.",
|
|
972
|
+
});
|
|
839
973
|
}
|
|
840
974
|
const label = `v${configVersion.version_number}`;
|
|
841
975
|
return { versionId: configVersion.id, label, agentId: agentInfo.id };
|
|
@@ -844,8 +978,9 @@ async function resolveVersionDraft(config, projectConfig, session, explicitVersi
|
|
|
844
978
|
// Auto-detect drafts
|
|
845
979
|
const drafts = allVersions.filter((v) => v.status === "draft");
|
|
846
980
|
if (drafts.length === 0) {
|
|
847
|
-
|
|
848
|
-
|
|
981
|
+
throw new ValidationError("No draft version found — you can only push to a draft version.", {
|
|
982
|
+
suggestion: "Create a new draft (`aui version create`), import it (`aui import-agent --version <draft-id>`), then `aui push`.",
|
|
983
|
+
});
|
|
849
984
|
}
|
|
850
985
|
if (drafts.length === 1) {
|
|
851
986
|
const draft = drafts[0];
|
|
@@ -894,6 +1029,8 @@ async function pushSnapshot(client, agentId, versionId, projectRoot, fileData) {
|
|
|
894
1029
|
}
|
|
895
1030
|
// ─── Agent Settings Params Resolution ───
|
|
896
1031
|
async function resolveAgentSettingsParams(config, projectConfig, session, projectRoot, scopeLevel) {
|
|
1032
|
+
// Throws ConfigError on any missing config field — never returns null.
|
|
1033
|
+
// See note on resolveVersionDraft for why silent returns are forbidden.
|
|
897
1034
|
const networkId = projectConfig.agent_id || session.network_id;
|
|
898
1035
|
const accountId = projectConfig.account_id || config.accountId;
|
|
899
1036
|
const organizationId = projectConfig.organization_id || config.organizationId;
|
|
@@ -906,8 +1043,9 @@ async function resolveAgentSettingsParams(config, projectConfig, session, projec
|
|
|
906
1043
|
missing.push("account_id (in .auirc or session)");
|
|
907
1044
|
if (!organizationId)
|
|
908
1045
|
missing.push("organization_id (in .auirc or session)");
|
|
909
|
-
|
|
910
|
-
|
|
1046
|
+
throw new ConfigError(`Missing: ${missing.join(", ")}`, {
|
|
1047
|
+
suggestion: "Re-import the agent (`aui import`) or re-login (`aui login`).",
|
|
1048
|
+
});
|
|
911
1049
|
}
|
|
912
1050
|
let categoryId = projectConfig.network_category_id;
|
|
913
1051
|
if (!categoryId) {
|
|
@@ -931,8 +1069,9 @@ async function resolveAgentSettingsParams(config, projectConfig, session, projec
|
|
|
931
1069
|
}
|
|
932
1070
|
}
|
|
933
1071
|
if (!categoryId) {
|
|
934
|
-
|
|
935
|
-
|
|
1072
|
+
throw new ConfigError("Missing network_category_id.", {
|
|
1073
|
+
suggestion: "Re-import the agent (`aui import`) to fix.",
|
|
1074
|
+
});
|
|
936
1075
|
}
|
|
937
1076
|
const baseParams = {
|
|
938
1077
|
updated_by: userId,
|