run402 4.80.0 → 4.80.1
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/README.md +22 -4
- package/gitvault-surface.json +1 -1
- package/lib/command-manifest.mjs +4 -1
- package/lib/deploy-v2.mjs +18 -2
- package/lib/doctor.mjs +164 -30
- package/lib/functions.mjs +153 -42
- package/lib/gitvault-scaffold.mjs +19 -7
- package/lib/harness-context.mjs +6 -2
- package/lib/image.mjs +3 -1
- package/lib/init.mjs +5 -3
- package/lib/logs.mjs +125 -96
- package/lib/repos.mjs +40 -8
- package/lib/up.mjs +67 -21
- package/package.json +1 -1
- package/sdk/dist/actions.d.ts +27 -9
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/errors.d.ts +1 -1
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +1 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +1 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +35 -3
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +15 -8
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts +17 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +131 -2
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/functions.types.d.ts +62 -0
- package/sdk/dist/namespaces/functions.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +38 -0
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +122 -14
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +176 -16
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/client-detect.d.ts +16 -1
- package/sdk/dist/node/client-detect.d.ts.map +1 -1
- package/sdk/dist/node/client-detect.js +26 -9
- package/sdk/dist/node/client-detect.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +42 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +175 -1
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +2 -2
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +1 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/scoped.d.ts +2 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +3 -0
- package/sdk/dist/scoped.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions-node.d.ts","sourceRoot":"","sources":["../../src/node/actions-node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"actions-node.d.ts","sourceRoot":"","sources":["../../src/node/actions-node.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAIV,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,EAGb,kCAAkC,EAClC,wBAAwB,EACxB,mBAAmB,EACnB,cAAc,EAGf,MAAM,eAAe,CAAC;AAoBvB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAU1C,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,uBAAuB,CAAC;AASrE,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAmKD;;;GAGG;AAEH,qBAAa,WAAY,YAAW,aAAa;;IAE7C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,kBAAuB;IAG1C,GAAG,CACP,KAAK,EAAE,kCAAkC,EACzC,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzC,GAAG,CACP,KAAK,EAAE,wBAAwB,EAC/B,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvC,GAAG,CACP,KAAK,EAAE,mBAAmB,EAC1B,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC;IAmCxC,EAAE,CACN,KAAK,GAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAM,EAC7C,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC;CAgqE/C"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
3
|
+
import { lstat, mkdir, mkdtemp, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve as resolvePath } from "node:path";
|
|
6
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -8,7 +8,7 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import { Run402Action } from "../actions.js";
|
|
9
9
|
import { RUN402_APP_SCHEMA_ID, RUN402_APP_MANIFEST_FILENAME, compileRun402AppInstallGraph, createRun402AppUpResult, } from "../app-up.js";
|
|
10
10
|
import { LocalError, Run402DeployError } from "../errors.js";
|
|
11
|
-
import { loadDeployManifest, normalizeDeployManifest } from "./deploy-manifest.js";
|
|
11
|
+
import { assertLocalFileReferencesExist, loadDeployManifest, manifestNotFoundError, normalizeDeployManifest, } from "./deploy-manifest.js";
|
|
12
12
|
import { declaredAgentName, detectClientName } from "./client-detect.js";
|
|
13
13
|
function deployEventDetails(event) {
|
|
14
14
|
const raw = event;
|
|
@@ -400,9 +400,13 @@ export class NodeActions {
|
|
|
400
400
|
* principal-display-name (first-deploy-agent-dx): make sure the deploying
|
|
401
401
|
* principal has a display name before the deploy that will be credited to
|
|
402
402
|
* it, then join the project's room under that name. Explicit
|
|
403
|
-
* `identityName`
|
|
404
|
-
*
|
|
405
|
-
*
|
|
403
|
+
* `identityName` / `RUN402_AGENT_NAME` wins and may rename an already-named
|
|
404
|
+
* principal; otherwise a detected client name is set only when no name
|
|
405
|
+
* exists yet, and reported as `detected`. The detected client is always
|
|
406
|
+
* reported (`detected` + `detection`), even when it was not applied, so an
|
|
407
|
+
* agent whose principal is already named sees why. Best-effort end to
|
|
408
|
+
* end: a whoami, PATCH, or room hiccup never fails the deploy — it reports
|
|
409
|
+
* `source: "unavailable"`.
|
|
406
410
|
*/
|
|
407
411
|
async #ensureIdentity(input, projectId, run) {
|
|
408
412
|
if (this.#targetKind() === "core") {
|
|
@@ -422,9 +426,20 @@ export class NodeActions {
|
|
|
422
426
|
let source = "existing";
|
|
423
427
|
// Order: explicit input, then the runtime's own declaration
|
|
424
428
|
// (RUN402_AGENT_NAME), then a specifically detected client. A generic
|
|
425
|
-
// guess is never persisted as a name.
|
|
429
|
+
// guess is never persisted as a name. Detection always runs so the
|
|
430
|
+
// result can say what was seen even when it was not applied.
|
|
426
431
|
const explicitName = input.identityName?.trim() || declaredAgentName() || null;
|
|
427
|
-
const detectedName =
|
|
432
|
+
const detectedName = detectClientName();
|
|
433
|
+
const detectionReport = (applied) => {
|
|
434
|
+
const reason = applied
|
|
435
|
+
? "applied"
|
|
436
|
+
: !detectedName
|
|
437
|
+
? "nothing_detected"
|
|
438
|
+
: explicitName
|
|
439
|
+
? "explicit_name_wins"
|
|
440
|
+
: "name_already_set";
|
|
441
|
+
return { detected: detectedName, detection: { applied, reason } };
|
|
442
|
+
};
|
|
428
443
|
if (!displayName || (explicitName && explicitName !== displayName)) {
|
|
429
444
|
const desired = explicitName ?? detectedName;
|
|
430
445
|
if (!desired) {
|
|
@@ -439,12 +454,12 @@ export class NodeActions {
|
|
|
439
454
|
}
|
|
440
455
|
run.skipStep({
|
|
441
456
|
action: "identity.name.set",
|
|
442
|
-
description: "No display name is known for this principal; nothing was written (set RUN402_AGENT_NAME or run `run402 org whoami --set-name <name>`)",
|
|
457
|
+
description: "No display name is known for this principal; nothing was written (set RUN402_AGENT_NAME=<name>, RUN402_CLIENT=<client>, or run `run402 org whoami --set-name <name>`)",
|
|
443
458
|
mutation: false,
|
|
444
459
|
auto: true,
|
|
445
|
-
details: { source: "undetected" },
|
|
460
|
+
details: { source: "undetected", detected: null },
|
|
446
461
|
});
|
|
447
|
-
return { display_name: null, source: "undetected", presence };
|
|
462
|
+
return { display_name: null, source: "undetected", ...detectionReport(false), presence };
|
|
448
463
|
}
|
|
449
464
|
source = explicitName ? "explicit" : "detected";
|
|
450
465
|
const step = run.addStep({
|
|
@@ -452,7 +467,7 @@ export class NodeActions {
|
|
|
452
467
|
description: `Set this principal's display name to ${desired}`,
|
|
453
468
|
mutation: true,
|
|
454
469
|
auto: true,
|
|
455
|
-
details: { display_name: desired, source },
|
|
470
|
+
details: { display_name: desired, source, detected: detectedName },
|
|
456
471
|
});
|
|
457
472
|
await run.approve(step, ["identity.name.set"], `Name this principal "${desired}" (promotion credit and room presence use it; change any time with run402 org whoami --set-name).`);
|
|
458
473
|
run.setState(step, "running");
|
|
@@ -463,9 +478,20 @@ export class NodeActions {
|
|
|
463
478
|
}
|
|
464
479
|
catch (err) {
|
|
465
480
|
run.setState(step, "failed", { error: err instanceof Error ? err.message : String(err) });
|
|
466
|
-
return { display_name: current, source: "unavailable" };
|
|
481
|
+
return { display_name: current, source: "unavailable", ...detectionReport(false) };
|
|
467
482
|
}
|
|
468
483
|
}
|
|
484
|
+
else if (detectedName && detectedName !== displayName) {
|
|
485
|
+
// A client was detected but the principal already carries a name: say
|
|
486
|
+
// so, write nothing (an existing name is never overwritten by a guess).
|
|
487
|
+
run.skipStep({
|
|
488
|
+
action: "identity.name.set",
|
|
489
|
+
description: `Detected client "${detectedName}" but the principal is already named "${displayName}"; nothing written — set RUN402_AGENT_NAME=<name> or run \`run402 org whoami --set-name <name>\` to rename`,
|
|
490
|
+
mutation: false,
|
|
491
|
+
auto: true,
|
|
492
|
+
details: { source: "existing", display_name: displayName, detected: detectedName, reason: "name_already_set" },
|
|
493
|
+
});
|
|
494
|
+
}
|
|
469
495
|
let presence = null;
|
|
470
496
|
try {
|
|
471
497
|
const room = await this.sdk.rooms.forProject(projectId);
|
|
@@ -478,7 +504,7 @@ export class NodeActions {
|
|
|
478
504
|
catch {
|
|
479
505
|
presence = null;
|
|
480
506
|
}
|
|
481
|
-
return { display_name: displayName, source, presence };
|
|
507
|
+
return { display_name: displayName, source, ...detectionReport(source === "detected"), presence };
|
|
482
508
|
}
|
|
483
509
|
async #verifyDeployManifestOnly(input, manifest, workspaceDir, run) {
|
|
484
510
|
const checks = manifest.verify?.http ?? [];
|
|
@@ -557,11 +583,28 @@ export class NodeActions {
|
|
|
557
583
|
},
|
|
558
584
|
},
|
|
559
585
|
};
|
|
560
|
-
|
|
586
|
+
// `up` never walks into subdirectories, but an agent standing in a
|
|
587
|
+
// parent workspace (a monorepo root, a checkout that holds the app one
|
|
588
|
+
// level down) usually is one `--dir` away from the manifest it meant.
|
|
589
|
+
// Name what is one directory down so the hop is executable.
|
|
590
|
+
const nearby = await findNearbyManifests(workspaceDir);
|
|
591
|
+
const nearbyActions = nearby.map((entry) => ({
|
|
592
|
+
type: "run_in_directory",
|
|
593
|
+
command: `run402 up --check --dir ${shellArg(entry.relative_dir)}`,
|
|
594
|
+
argv: ["run402", "up", "--check", "--dir", entry.relative_dir],
|
|
595
|
+
path: entry.path,
|
|
596
|
+
dir: entry.relative_dir,
|
|
597
|
+
why: "A manifest exists one directory down; up does not walk into subdirectories.",
|
|
598
|
+
}));
|
|
599
|
+
throw run.error(nearby.length > 0
|
|
600
|
+
? `No deploy manifest found in ${workspaceDir}, but ${nearby.length === 1 ? "one exists" : `${nearby.length} exist`} one directory down (${nearby.map((entry) => entry.relative_dir).join(", ")}). Re-run with --dir <that directory>, or add run402.json, run402.deploy.json or app.json here.`
|
|
601
|
+
: "No deploy manifest found. Add run402.json, run402.deploy.json or app.json, or pass --manifest for executable configs.", "UP_MANIFEST_REQUIRED", {
|
|
561
602
|
dir: workspaceDir,
|
|
562
603
|
candidates: MANIFEST_CANDIDATES,
|
|
563
604
|
executable_candidates: EXECUTABLE_MANIFEST_CANDIDATES,
|
|
605
|
+
nearby_manifests: nearby,
|
|
564
606
|
next_actions: [
|
|
607
|
+
...nearbyActions,
|
|
565
608
|
{
|
|
566
609
|
type: "create_manifest",
|
|
567
610
|
why: "Write a deploy manifest, then re-run. This minimal site manifest deploys as-is; add database/functions/routes slices later.",
|
|
@@ -621,11 +664,23 @@ export class NodeActions {
|
|
|
621
664
|
}
|
|
622
665
|
throw err;
|
|
623
666
|
}
|
|
667
|
+
// The release slice is normally normalized only after the build
|
|
668
|
+
// ran. With no build commands declared nothing on disk changes
|
|
669
|
+
// between now and then, so the slice's filesystem references can be
|
|
670
|
+
// checked right here — in `--check` as much as before a real apply.
|
|
671
|
+
// A declared build may produce the referenced files (there is no
|
|
672
|
+
// output-dir field to narrow by), so the pre-build check is skipped
|
|
673
|
+
// then and the post-build normalization performs it instead.
|
|
674
|
+
const buildProducesFiles = (appSpec.build?.commands ?? []).length > 0;
|
|
675
|
+
if (!buildProducesFiles) {
|
|
676
|
+
await this.#assertAppReleaseFileReferences(appSpec, manifestPath, run);
|
|
677
|
+
}
|
|
624
678
|
run.setState(step, "succeeded", {
|
|
625
679
|
manifest_kind: "app",
|
|
626
680
|
manifest_path: manifestPath,
|
|
627
681
|
app_id: appSpec.app.id,
|
|
628
682
|
graph_digest: appGraph.graph_digest,
|
|
683
|
+
file_references: buildProducesFiles ? "deferred_to_post_build" : "checked",
|
|
629
684
|
});
|
|
630
685
|
return {
|
|
631
686
|
manifestKind: "app",
|
|
@@ -645,6 +700,7 @@ export class NodeActions {
|
|
|
645
700
|
if (!hasDeployableContent(loaded.spec)) {
|
|
646
701
|
throw run.error("Deploy manifest contains no deployable sections.", "MANIFEST_EMPTY", { manifest_path: manifestPath });
|
|
647
702
|
}
|
|
703
|
+
await this.#assertManifestFileReferences(loaded.spec, manifestPath, run);
|
|
648
704
|
run.setState(step, "succeeded", {
|
|
649
705
|
manifest_kind: "release",
|
|
650
706
|
manifest_path: manifestPath,
|
|
@@ -722,6 +778,73 @@ export class NodeActions {
|
|
|
722
778
|
},
|
|
723
779
|
};
|
|
724
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* `stat` every local filesystem reference of a normalized spec and fail
|
|
783
|
+
* with the action-shaped `MANIFEST_FILE_MISSING` envelope (code, message
|
|
784
|
+
* naming the first missing file, `details.missing[]`, one `create_file`
|
|
785
|
+
* next action per file) before any plan or mutation.
|
|
786
|
+
*/
|
|
787
|
+
async #assertManifestFileReferences(spec, manifestPath, run) {
|
|
788
|
+
try {
|
|
789
|
+
await assertLocalFileReferencesExist(spec, { manifestPath });
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
throw this.#retypeManifestFileError(err, run);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Normalize an app release slice and re-shape a `MANIFEST_FILE_MISSING`
|
|
797
|
+
* (a missing migration `sql_path`) into the action envelope; every other
|
|
798
|
+
* normalization error passes through untouched.
|
|
799
|
+
*/
|
|
800
|
+
async #normalizeWithTypedFileErrors(fn, run) {
|
|
801
|
+
try {
|
|
802
|
+
return await fn();
|
|
803
|
+
}
|
|
804
|
+
catch (err) {
|
|
805
|
+
if (err instanceof LocalError && err.code === "MANIFEST_FILE_MISSING") {
|
|
806
|
+
throw this.#retypeManifestFileError(err, run);
|
|
807
|
+
}
|
|
808
|
+
throw err;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
#retypeManifestFileError(err, run) {
|
|
812
|
+
if (!(err instanceof LocalError) || err.code !== "MANIFEST_FILE_MISSING")
|
|
813
|
+
return err;
|
|
814
|
+
const details = err.details && typeof err.details === "object" && !Array.isArray(err.details)
|
|
815
|
+
? err.details
|
|
816
|
+
: {};
|
|
817
|
+
return run.error(err.message, "MANIFEST_FILE_MISSING", {
|
|
818
|
+
...details,
|
|
819
|
+
next_actions: err.nextActions ?? [],
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Pre-build filesystem check for an app manifest's release slice. Templates
|
|
824
|
+
* are materialized against an empty environment (only path-bearing fields
|
|
825
|
+
* matter here) and the slice is normalized exactly as the post-build path
|
|
826
|
+
* would; a `MANIFEST_FILE_MISSING` surfaces, any other normalization error
|
|
827
|
+
* is left for the real apply path to report with its full context.
|
|
828
|
+
*/
|
|
829
|
+
async #assertAppReleaseFileReferences(appSpec, manifestPath, run) {
|
|
830
|
+
if (!appSpec.release || typeof appSpec.release !== "object")
|
|
831
|
+
return;
|
|
832
|
+
const releaseInput = materializeTemplates(appSpec.release, {});
|
|
833
|
+
let normalized;
|
|
834
|
+
try {
|
|
835
|
+
normalized = await normalizeDeployManifest(releaseInput, {
|
|
836
|
+
project: "prj_up_preflight_placeholder",
|
|
837
|
+
baseDir: dirname(manifestPath),
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
catch (err) {
|
|
841
|
+
if (err instanceof LocalError && err.code === "MANIFEST_FILE_MISSING") {
|
|
842
|
+
throw this.#retypeManifestFileError(err, run);
|
|
843
|
+
}
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
await this.#assertManifestFileReferences(normalized.spec, manifestPath, run);
|
|
847
|
+
}
|
|
725
848
|
#planAppUpResult(input, manifest, run, opts) {
|
|
726
849
|
if (!manifest.appGraph) {
|
|
727
850
|
throw run.error("Internal error: app manifest did not produce an install graph.", "RUN402_ACTION_INTERNAL");
|
|
@@ -957,13 +1080,14 @@ export class NodeActions {
|
|
|
957
1080
|
...env,
|
|
958
1081
|
"input.name": input.name ?? manifest.appSpec.project.name ?? "",
|
|
959
1082
|
});
|
|
960
|
-
const normalized = await normalizeDeployManifest(releaseInput, {
|
|
1083
|
+
const normalized = await this.#normalizeWithTypedFileErrors(() => normalizeDeployManifest(releaseInput, {
|
|
961
1084
|
project: resolved.projectId,
|
|
962
1085
|
baseDir: dirname(manifest.manifestPath),
|
|
963
|
-
});
|
|
1086
|
+
}), run);
|
|
964
1087
|
if (!hasDeployableContent(normalized.spec)) {
|
|
965
1088
|
throw run.error("App release contains no deployable sections.", "MANIFEST_EMPTY", { manifest_path: manifest.manifestPath });
|
|
966
1089
|
}
|
|
1090
|
+
await this.#assertManifestFileReferences(normalized.spec, manifest.manifestPath, run);
|
|
967
1091
|
const deployStep = run.addStep({
|
|
968
1092
|
action: "deploy.apply",
|
|
969
1093
|
description: "Apply app release",
|
|
@@ -2007,9 +2131,14 @@ class ActionRun {
|
|
|
2007
2131
|
};
|
|
2008
2132
|
}
|
|
2009
2133
|
error(message, code, details) {
|
|
2134
|
+
// `details.next_actions` stays where consumers already read it; hoist a
|
|
2135
|
+
// copy to the envelope's top-level `next_actions` so `err.nextActions` /
|
|
2136
|
+
// the CLI's `next_actions` field carry it too.
|
|
2137
|
+
const nextActions = Array.isArray(details?.next_actions) ? details.next_actions : undefined;
|
|
2010
2138
|
return withActionDetails(new LocalError(message, "running Run402 action", {
|
|
2011
2139
|
code,
|
|
2012
2140
|
details: { action: this.input.type, ...(details ?? {}), steps: this.steps },
|
|
2141
|
+
...(nextActions ? { next_actions: nextActions } : {}),
|
|
2013
2142
|
}), this);
|
|
2014
2143
|
}
|
|
2015
2144
|
emit(step) {
|
|
@@ -2494,6 +2623,34 @@ async function findManifest(workspaceDir) {
|
|
|
2494
2623
|
}
|
|
2495
2624
|
return null;
|
|
2496
2625
|
}
|
|
2626
|
+
const NEARBY_MANIFEST_SCAN_LIMIT = 200;
|
|
2627
|
+
/**
|
|
2628
|
+
* Manifests one directory down from `workspaceDir` (immediate children
|
|
2629
|
+
* only; `node_modules`, `.git` and every other dot-directory skipped; the
|
|
2630
|
+
* scan reads at most {@link NEARBY_MANIFEST_SCAN_LIMIT} entries). Surfaced in
|
|
2631
|
+
* `UP_MANIFEST_REQUIRED` so an agent standing in a parent directory learns
|
|
2632
|
+
* where `up` would have found a manifest instead of a bare ENOENT.
|
|
2633
|
+
*/
|
|
2634
|
+
async function findNearbyManifests(workspaceDir) {
|
|
2635
|
+
let entries;
|
|
2636
|
+
try {
|
|
2637
|
+
entries = await readdir(workspaceDir, { withFileTypes: true });
|
|
2638
|
+
}
|
|
2639
|
+
catch {
|
|
2640
|
+
return [];
|
|
2641
|
+
}
|
|
2642
|
+
const found = [];
|
|
2643
|
+
for (const entry of entries.slice(0, NEARBY_MANIFEST_SCAN_LIMIT)) {
|
|
2644
|
+
if (!entry.isDirectory())
|
|
2645
|
+
continue;
|
|
2646
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
2647
|
+
continue;
|
|
2648
|
+
const manifestPath = await findManifest(join(workspaceDir, entry.name));
|
|
2649
|
+
if (manifestPath)
|
|
2650
|
+
found.push({ path: manifestPath, relative_dir: entry.name });
|
|
2651
|
+
}
|
|
2652
|
+
return found.sort((a, b) => a.relative_dir.localeCompare(b.relative_dir));
|
|
2653
|
+
}
|
|
2497
2654
|
async function findExecutableManifest(workspaceDir) {
|
|
2498
2655
|
for (const candidate of EXECUTABLE_MANIFEST_CANDIDATES) {
|
|
2499
2656
|
const path = join(workspaceDir, candidate);
|
|
@@ -2513,6 +2670,9 @@ async function loadRun402JsonManifest(path) {
|
|
|
2513
2670
|
raw = await readFile(path, "utf-8");
|
|
2514
2671
|
}
|
|
2515
2672
|
catch (err) {
|
|
2673
|
+
if (err?.code === "ENOENT") {
|
|
2674
|
+
throw manifestNotFoundError(path, "loading Run402 manifest");
|
|
2675
|
+
}
|
|
2516
2676
|
throw new LocalError(`Failed to read Run402 manifest '${path}': ${err.message}`, "loading Run402 manifest", err);
|
|
2517
2677
|
}
|
|
2518
2678
|
try {
|