@tetsuo-ai/agenc 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/generated/agenc-runtime-manifest.json +7 -7
- package/generated/agenc-runtime-manifest.json.sig +1 -1
- package/generated/agenc-runtime-public-key.pem +1 -1
- package/generated/agenc-runtime-trust-policy.json +3 -3
- package/lib/cli.js +76 -2
- package/lib/runtime-manager.js +281 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,14 @@ agenc
|
|
|
12
12
|
agenc ui
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
`agenc onboard` is the canonical first-run path. It opens an interactive
|
|
16
|
+
terminal onboarding flow that:
|
|
17
|
+
|
|
18
|
+
- validates an xAI API key
|
|
19
|
+
- lets the operator shape the agent name, mission, role, soul, and tool posture
|
|
20
|
+
- writes the canonical config at `~/.agenc/config.json`
|
|
21
|
+
- generates the curated workspace markdown profile under `~/.agenc/workspace/`
|
|
22
|
+
|
|
15
23
|
It does not expose the runtime source tree directly. Instead, it installs and
|
|
16
24
|
launches the matching AgenC runtime artifact for the current supported
|
|
17
25
|
platform.
|
|
@@ -83,6 +91,52 @@ agenc runtime update
|
|
|
83
91
|
agenc runtime uninstall
|
|
84
92
|
```
|
|
85
93
|
|
|
94
|
+
## First-use devnet marketplace rehearsal
|
|
95
|
+
|
|
96
|
+
For public release-path docs and operator rehearsal, use the top-level `agenc`
|
|
97
|
+
commands. `agenc-runtime` remains available as a compatibility alias after the
|
|
98
|
+
runtime is installed, but it is not the primary public wrapper path.
|
|
99
|
+
|
|
100
|
+
Supported release-path boundary:
|
|
101
|
+
|
|
102
|
+
- Linux `x64`
|
|
103
|
+
- Node `>=18.0.0`
|
|
104
|
+
|
|
105
|
+
Manual prerequisites that still live outside the wrapper:
|
|
106
|
+
|
|
107
|
+
- Solana CLI available on `PATH`
|
|
108
|
+
- funded devnet keypair at `SOLANA_KEYPAIR_PATH` or `~/.config/solana/id.json`
|
|
109
|
+
- `--rpc` or `AGENC_RUNTIME_RPC_URL`
|
|
110
|
+
- optional `--program-id` or `AGENC_RUNTIME_PROGRAM_ID` when testing a non-default deployment
|
|
111
|
+
- a second funded signer plus a second agent registration if you want a separate worker identity for `claim` and `complete`
|
|
112
|
+
|
|
113
|
+
Minimal creator flow:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
export AGENC_RUNTIME_RPC_URL=https://api.devnet.solana.com
|
|
117
|
+
agenc onboard
|
|
118
|
+
agenc runtime install
|
|
119
|
+
agenc start
|
|
120
|
+
agenc agent register --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
121
|
+
agenc market tasks create --description "public task" --reward 50000000 --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
122
|
+
agenc market tasks list --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
123
|
+
agenc market tui --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Claim and complete from a worker signer:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export SOLANA_KEYPAIR_PATH=/path/to/worker.json
|
|
130
|
+
agenc agent register --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
131
|
+
agenc market tasks claim <taskPda> --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
132
|
+
agenc market tasks complete <taskPda> --result-data "completed via public wrapper" --rpc "$AGENC_RUNTIME_RPC_URL"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Notes:
|
|
136
|
+
|
|
137
|
+
- `tasks claim|complete|dispute` require the signer wallet to already control a registered agent.
|
|
138
|
+
- `disputes resolve` is not part of the public first-use rehearsal; it requires the protocol authority wallet.
|
|
139
|
+
|
|
86
140
|
## Development
|
|
87
141
|
|
|
88
142
|
The embedded runtime manifest in `generated/` is produced by the `agenc-core`
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 1,
|
|
3
|
-
"wrapperVersion": "0.
|
|
4
|
-
"keyId": "
|
|
5
|
-
"generatedAt": "2026-
|
|
3
|
+
"wrapperVersion": "0.2.0",
|
|
4
|
+
"keyId": "agenc-v0.2.0",
|
|
5
|
+
"generatedAt": "2026-04-12T08:17:12.539Z",
|
|
6
6
|
"releaseChannel": "github-releases",
|
|
7
7
|
"releaseRepository": "tetsuo-ai/agenc-core",
|
|
8
|
-
"releaseTag": "v0.
|
|
8
|
+
"releaseTag": "agenc-v0.2.0",
|
|
9
9
|
"artifacts": [
|
|
10
10
|
{
|
|
11
11
|
"platform": "linux",
|
|
12
12
|
"arch": "x64",
|
|
13
13
|
"nodeRange": ">=18.0.0",
|
|
14
|
-
"runtimeVersion": "0.
|
|
15
|
-
"url": "https://github.com/tetsuo-ai/agenc-core/releases/download/v0.
|
|
16
|
-
"sha256": "
|
|
14
|
+
"runtimeVersion": "0.2.0",
|
|
15
|
+
"url": "https://github.com/tetsuo-ai/agenc-core/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-linux-x64.tar.gz",
|
|
16
|
+
"sha256": "67de5ba2c5bced79893414e226547016325d423dd630a25ff579b8cc6abbb76d",
|
|
17
17
|
"bins": {
|
|
18
18
|
"agenc": "node_modules/@tetsuo-ai/runtime/dist/bin/agenc.js",
|
|
19
19
|
"agenc-runtime": "node_modules/@tetsuo-ai/runtime/dist/bin/agenc-runtime.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
nyIC/iBXuyr5jy7GThy5GsYjD9GgmmnSnbD54CpupYwr97qdAhmRuxrA0cqxOegFwN1pAu6Lu+qOYHyaePBEAw==
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"wrapperVersion": "0.
|
|
3
|
-
"keyId": "
|
|
2
|
+
"wrapperVersion": "0.2.0",
|
|
3
|
+
"keyId": "agenc-v0.2.0",
|
|
4
4
|
"releaseChannel": "github-releases",
|
|
5
5
|
"releaseRepository": "tetsuo-ai/agenc-core",
|
|
6
|
-
"releaseTag": "v0.
|
|
6
|
+
"releaseTag": "agenc-v0.2.0",
|
|
7
7
|
"revokedManifestDigests": [],
|
|
8
8
|
"revokedRuntimeVersions": []
|
|
9
9
|
}
|
package/lib/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import {
|
|
3
3
|
describeRuntimeInstall,
|
|
4
|
+
ensureRuntimeFromSource,
|
|
4
5
|
ensureRuntimeInstalled,
|
|
5
6
|
RuntimeInstallError,
|
|
6
7
|
spawnInstalledRuntimeBin,
|
|
8
|
+
spawnInstalledRuntimeBinFromSource,
|
|
7
9
|
uninstallRuntime,
|
|
8
10
|
} from "./runtime-manager.js";
|
|
9
11
|
|
|
@@ -23,6 +25,22 @@ function buildAgencHelp() {
|
|
|
23
25
|
" agenc status",
|
|
24
26
|
" agenc logs",
|
|
25
27
|
" agenc runtime where",
|
|
28
|
+
"",
|
|
29
|
+
"Marketplace / devnet examples:",
|
|
30
|
+
" agenc agent register --rpc https://api.devnet.solana.com",
|
|
31
|
+
" agenc market tasks create --description 'public task' --reward 50000000 --rpc https://api.devnet.solana.com",
|
|
32
|
+
" agenc market tui",
|
|
33
|
+
"",
|
|
34
|
+
"Supported public wrapper tuple:",
|
|
35
|
+
" Linux x64, Node >=18.0.0",
|
|
36
|
+
"",
|
|
37
|
+
"Compatibility alias:",
|
|
38
|
+
" agenc-runtime remains available after install, but public docs should use agenc.",
|
|
39
|
+
"",
|
|
40
|
+
"Dev / from-source:",
|
|
41
|
+
" agenc start --from-source",
|
|
42
|
+
" agenc start --from-source --source-dir /path/to/agenc-core/runtime",
|
|
43
|
+
" Builds runtime/dist locally and points ~/.agenc/runtime/current at it.",
|
|
26
44
|
].join("\n");
|
|
27
45
|
}
|
|
28
46
|
|
|
@@ -31,6 +49,11 @@ function buildAgencRuntimeHelp() {
|
|
|
31
49
|
"agenc-runtime [runtime-command] [options]",
|
|
32
50
|
"",
|
|
33
51
|
"Compatibility alias for the installed AgenC runtime CLI.",
|
|
52
|
+
"",
|
|
53
|
+
"Dev flags:",
|
|
54
|
+
" --from-source build the local agenc-core/runtime tree",
|
|
55
|
+
" and run the daemon from there",
|
|
56
|
+
" --source-dir <path> explicit runtime/ source path",
|
|
34
57
|
].join("\n");
|
|
35
58
|
}
|
|
36
59
|
|
|
@@ -116,11 +139,44 @@ async function runRuntimeAdmin(argv, io, deps, runtimeOptions) {
|
|
|
116
139
|
|
|
117
140
|
const DEFAULT_DEPS = {
|
|
118
141
|
ensureRuntimeInstalled,
|
|
142
|
+
ensureRuntimeFromSource,
|
|
119
143
|
describeRuntimeInstall,
|
|
120
144
|
uninstallRuntime,
|
|
121
145
|
spawnInstalledRuntimeBin,
|
|
146
|
+
spawnInstalledRuntimeBinFromSource,
|
|
122
147
|
};
|
|
123
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Cut 6.1: detect and strip the `--from-source` flag (and the optional
|
|
151
|
+
* `--source-dir <path>`) from the wrapper argv. Returns the remaining argv
|
|
152
|
+
* plus the parsed flag values so the rest of the dispatcher can continue
|
|
153
|
+
* to operate on a clean argument list.
|
|
154
|
+
*/
|
|
155
|
+
function extractFromSourceFlags(argv) {
|
|
156
|
+
const remaining = [];
|
|
157
|
+
let fromSource = false;
|
|
158
|
+
let sourceDir;
|
|
159
|
+
for (let i = 0; i < argv.length; i++) {
|
|
160
|
+
const arg = argv[i];
|
|
161
|
+
if (arg === "--from-source") {
|
|
162
|
+
fromSource = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (arg === "--source-dir" && i + 1 < argv.length) {
|
|
166
|
+
sourceDir = argv[++i];
|
|
167
|
+
fromSource = true;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (typeof arg === "string" && arg.startsWith("--source-dir=")) {
|
|
171
|
+
sourceDir = arg.slice("--source-dir=".length);
|
|
172
|
+
fromSource = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
remaining.push(arg);
|
|
176
|
+
}
|
|
177
|
+
return { argv: remaining, fromSource, sourceDir };
|
|
178
|
+
}
|
|
179
|
+
|
|
124
180
|
function formatRuntimeInstallError(error) {
|
|
125
181
|
if (error instanceof RuntimeInstallError) {
|
|
126
182
|
return error.message;
|
|
@@ -129,7 +185,8 @@ function formatRuntimeInstallError(error) {
|
|
|
129
185
|
}
|
|
130
186
|
|
|
131
187
|
export async function runAgencWrapper(options = {}, deps = DEFAULT_DEPS) {
|
|
132
|
-
const
|
|
188
|
+
const rawArgv = options.argv ?? process.argv.slice(2);
|
|
189
|
+
const { argv, fromSource, sourceDir } = extractFromSourceFlags(rawArgv);
|
|
133
190
|
const stdout = options.stdout ?? process.stdout;
|
|
134
191
|
const stderr = options.stderr ?? process.stderr;
|
|
135
192
|
const runtimeOptions = {
|
|
@@ -137,6 +194,7 @@ export async function runAgencWrapper(options = {}, deps = DEFAULT_DEPS) {
|
|
|
137
194
|
cwd: options.cwd ?? process.cwd(),
|
|
138
195
|
homeDir: options.homeDir,
|
|
139
196
|
packageRoot: options.packageRoot,
|
|
197
|
+
sourceDir,
|
|
140
198
|
};
|
|
141
199
|
|
|
142
200
|
try {
|
|
@@ -152,6 +210,13 @@ export async function runAgencWrapper(options = {}, deps = DEFAULT_DEPS) {
|
|
|
152
210
|
writeLine(stdout, buildAgencHelp());
|
|
153
211
|
return 0;
|
|
154
212
|
}
|
|
213
|
+
if (fromSource) {
|
|
214
|
+
return await deps.spawnInstalledRuntimeBinFromSource(
|
|
215
|
+
"agenc",
|
|
216
|
+
argv,
|
|
217
|
+
runtimeOptions,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
155
220
|
return await deps.spawnInstalledRuntimeBin("agenc", argv, runtimeOptions);
|
|
156
221
|
} catch (error) {
|
|
157
222
|
writeLine(stderr, formatRuntimeInstallError(error));
|
|
@@ -160,7 +225,8 @@ export async function runAgencWrapper(options = {}, deps = DEFAULT_DEPS) {
|
|
|
160
225
|
}
|
|
161
226
|
|
|
162
227
|
export async function runAgencRuntimeWrapper(options = {}, deps = DEFAULT_DEPS) {
|
|
163
|
-
const
|
|
228
|
+
const rawArgv = options.argv ?? process.argv.slice(2);
|
|
229
|
+
const { argv, fromSource, sourceDir } = extractFromSourceFlags(rawArgv);
|
|
164
230
|
const stdout = options.stdout ?? process.stdout;
|
|
165
231
|
const stderr = options.stderr ?? process.stderr;
|
|
166
232
|
const runtimeOptions = {
|
|
@@ -168,6 +234,7 @@ export async function runAgencRuntimeWrapper(options = {}, deps = DEFAULT_DEPS)
|
|
|
168
234
|
cwd: options.cwd ?? process.cwd(),
|
|
169
235
|
homeDir: options.homeDir,
|
|
170
236
|
packageRoot: options.packageRoot,
|
|
237
|
+
sourceDir,
|
|
171
238
|
};
|
|
172
239
|
|
|
173
240
|
try {
|
|
@@ -175,6 +242,13 @@ export async function runAgencRuntimeWrapper(options = {}, deps = DEFAULT_DEPS)
|
|
|
175
242
|
writeLine(stdout, buildAgencRuntimeHelp());
|
|
176
243
|
return 0;
|
|
177
244
|
}
|
|
245
|
+
if (fromSource) {
|
|
246
|
+
return await deps.spawnInstalledRuntimeBinFromSource(
|
|
247
|
+
"agenc-runtime",
|
|
248
|
+
argv,
|
|
249
|
+
runtimeOptions,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
178
252
|
return await deps.spawnInstalledRuntimeBin(
|
|
179
253
|
"agenc-runtime",
|
|
180
254
|
argv,
|
package/lib/runtime-manager.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash, verify } from "node:crypto";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
copyFile,
|
|
5
|
+
lstat,
|
|
5
6
|
mkdir,
|
|
6
7
|
mkdtemp,
|
|
7
8
|
open,
|
|
@@ -36,6 +37,27 @@ export class RuntimeInstallError extends Error {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Remove a filesystem path regardless of whether it's a symlink, a
|
|
42
|
+
* regular file, or a directory. `fs.rm` with `recursive: true`
|
|
43
|
+
* follows symlinks to directories (removing the TARGET, not the
|
|
44
|
+
* link), so we `lstat` first and use `unlink` for the symlink case.
|
|
45
|
+
*/
|
|
46
|
+
async function removePathSafe(targetPath) {
|
|
47
|
+
let info;
|
|
48
|
+
try {
|
|
49
|
+
info = await lstat(targetPath);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if ((error && error.code) === "ENOENT") return;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (info.isSymbolicLink() || info.isFile()) {
|
|
55
|
+
await unlink(targetPath);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await rm(targetPath, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
function sleep(ms) {
|
|
40
62
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
63
|
}
|
|
@@ -829,3 +851,261 @@ export async function prefetchRuntimeOnInstall(options = {}) {
|
|
|
829
851
|
});
|
|
830
852
|
return { prefetched: true };
|
|
831
853
|
}
|
|
854
|
+
|
|
855
|
+
// ---------------------------------------------------------------------------
|
|
856
|
+
// Cut 6.1: --from-source dev mode
|
|
857
|
+
//
|
|
858
|
+
// The published-tarball install path lives above. This block adds a parallel
|
|
859
|
+
// path that builds the runtime from a local source checkout and points
|
|
860
|
+
// `~/.agenc/runtime/current` at it. Without this, the only way to land
|
|
861
|
+
// changes on the running daemon is to publish a new tarball — which is
|
|
862
|
+
// exactly the failure mode that left a 2-month silent-failure window in
|
|
863
|
+
// place. The dev path is opt-in via `--from-source` and never runs by
|
|
864
|
+
// default.
|
|
865
|
+
// ---------------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
const FROM_SOURCE_PLATFORM_ARCH = "from-source";
|
|
868
|
+
|
|
869
|
+
function resolveSourceRoot({
|
|
870
|
+
env = process.env,
|
|
871
|
+
cwd = process.cwd(),
|
|
872
|
+
explicit,
|
|
873
|
+
} = {}) {
|
|
874
|
+
const candidates = [];
|
|
875
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) {
|
|
876
|
+
candidates.push(path.resolve(explicit.trim()));
|
|
877
|
+
}
|
|
878
|
+
const envValue = env?.AGENC_RUNTIME_SOURCE_DIR;
|
|
879
|
+
if (typeof envValue === "string" && envValue.trim().length > 0) {
|
|
880
|
+
candidates.push(path.resolve(envValue.trim()));
|
|
881
|
+
}
|
|
882
|
+
// Walk upward from cwd looking for `runtime/package.json` whose name is
|
|
883
|
+
// `@tetsuo-ai/runtime` — that's the agenc-core monorepo root.
|
|
884
|
+
let current = path.resolve(cwd);
|
|
885
|
+
for (let i = 0; i < 8; i++) {
|
|
886
|
+
candidates.push(path.join(current, "runtime"));
|
|
887
|
+
const parent = path.dirname(current);
|
|
888
|
+
if (parent === current) break;
|
|
889
|
+
current = parent;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
for (const candidate of candidates) {
|
|
893
|
+
try {
|
|
894
|
+
const pkgPath = path.join(candidate, "package.json");
|
|
895
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
896
|
+
const parsed = JSON.parse(raw);
|
|
897
|
+
if (parsed?.name === "@tetsuo-ai/runtime") {
|
|
898
|
+
return candidate;
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
// try next
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function readJsonSafe(filePath) {
|
|
908
|
+
try {
|
|
909
|
+
const raw = await readFile(filePath, "utf8");
|
|
910
|
+
return JSON.parse(raw);
|
|
911
|
+
} catch {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function runBuildScript(sourceRoot, env) {
|
|
917
|
+
const result = spawn("npm", ["run", "build"], {
|
|
918
|
+
cwd: sourceRoot,
|
|
919
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
920
|
+
env: { ...env },
|
|
921
|
+
});
|
|
922
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
923
|
+
result.on("error", rejectPromise);
|
|
924
|
+
result.on("exit", (code) => {
|
|
925
|
+
if (code === 0) {
|
|
926
|
+
resolvePromise();
|
|
927
|
+
} else {
|
|
928
|
+
rejectPromise(
|
|
929
|
+
new RuntimeInstallError(
|
|
930
|
+
`from-source build failed with exit code ${code}`,
|
|
931
|
+
"from_source_build_failed",
|
|
932
|
+
),
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
async function readBuildVersion(sourceRoot) {
|
|
940
|
+
const versionPath = path.join(sourceRoot, "dist", "VERSION");
|
|
941
|
+
return await readJsonSafe(versionPath);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function buildFromSourceArtifactDescriptor(sourceRoot, packageJson, versionFile) {
|
|
945
|
+
const runtimeVersion =
|
|
946
|
+
typeof packageJson?.version === "string" ? packageJson.version : "0.0.0";
|
|
947
|
+
const distRoot = path.join(sourceRoot, "dist");
|
|
948
|
+
const bins = {
|
|
949
|
+
agenc: "bin/agenc.js",
|
|
950
|
+
"agenc-runtime": "bin/agenc-runtime.js",
|
|
951
|
+
daemon: "bin/daemon.js",
|
|
952
|
+
"agenc-watch": "bin/agenc-watch.js",
|
|
953
|
+
};
|
|
954
|
+
return {
|
|
955
|
+
runtimeVersion,
|
|
956
|
+
platform: FROM_SOURCE_PLATFORM_ARCH,
|
|
957
|
+
arch: "local",
|
|
958
|
+
bins,
|
|
959
|
+
sha256: versionFile?.commit ?? "from-source",
|
|
960
|
+
distRoot,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function ensureFromSourceCurrentPointer({
|
|
965
|
+
homeDir = os.homedir(),
|
|
966
|
+
distRoot,
|
|
967
|
+
}) {
|
|
968
|
+
const runtimeHome = getWrapperRuntimeHome(homeDir);
|
|
969
|
+
const releaseDir = path.join(
|
|
970
|
+
runtimeHome,
|
|
971
|
+
"releases",
|
|
972
|
+
"from-source",
|
|
973
|
+
FROM_SOURCE_PLATFORM_ARCH,
|
|
974
|
+
);
|
|
975
|
+
const currentDir = path.join(runtimeHome, "current");
|
|
976
|
+
const statePath = path.join(runtimeHome, "install-state.json");
|
|
977
|
+
|
|
978
|
+
await mkdir(path.dirname(releaseDir), { recursive: true });
|
|
979
|
+
await removePathSafe(releaseDir);
|
|
980
|
+
await mkdir(path.dirname(releaseDir), { recursive: true });
|
|
981
|
+
|
|
982
|
+
// The release dir is a directory containing a single symlink (`dist`)
|
|
983
|
+
// pointing back at the local source dist. The wrapper's bin paths look
|
|
984
|
+
// for `<releaseDir>/bin/<binName>`, so we lay out the same shape using
|
|
985
|
+
// a symlink for the entire `dist` tree.
|
|
986
|
+
await symlink(distRoot, releaseDir, "dir");
|
|
987
|
+
|
|
988
|
+
// Use removePathSafe (lstat-based) so we delete the SYMLINK at
|
|
989
|
+
// `currentDir`, not the target it points at. The previous `rm
|
|
990
|
+
// recursive` call followed the symlink and either failed silently
|
|
991
|
+
// or removed the target dir contents while leaving the link intact,
|
|
992
|
+
// causing the from-source flag to silently keep pointing at a
|
|
993
|
+
// stale release.
|
|
994
|
+
await removePathSafe(currentDir);
|
|
995
|
+
await symlink(releaseDir, currentDir, "dir");
|
|
996
|
+
|
|
997
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
998
|
+
await writeFile(
|
|
999
|
+
statePath,
|
|
1000
|
+
`${JSON.stringify(
|
|
1001
|
+
{
|
|
1002
|
+
installedAt: new Date().toISOString(),
|
|
1003
|
+
manifestSource: "from-source",
|
|
1004
|
+
runtimeVersion: path.basename(distRoot),
|
|
1005
|
+
platform: FROM_SOURCE_PLATFORM_ARCH,
|
|
1006
|
+
arch: "local",
|
|
1007
|
+
releaseDir,
|
|
1008
|
+
currentDir,
|
|
1009
|
+
},
|
|
1010
|
+
null,
|
|
1011
|
+
2,
|
|
1012
|
+
)}\n`,
|
|
1013
|
+
"utf8",
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
return { runtimeHome, releaseDir, currentDir, statePath };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
export async function ensureRuntimeFromSource(options = {}) {
|
|
1020
|
+
const env = options.env ?? process.env;
|
|
1021
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
1022
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1023
|
+
const sourceRoot = resolveSourceRoot({
|
|
1024
|
+
env,
|
|
1025
|
+
cwd,
|
|
1026
|
+
explicit: options.sourceDir,
|
|
1027
|
+
});
|
|
1028
|
+
if (!sourceRoot) {
|
|
1029
|
+
throw new RuntimeInstallError(
|
|
1030
|
+
"could not locate the agenc-core runtime source directory; pass --source-dir <path> or set AGENC_RUNTIME_SOURCE_DIR",
|
|
1031
|
+
"from_source_root_not_found",
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
const distRoot = path.join(sourceRoot, "dist");
|
|
1035
|
+
const skipBuild =
|
|
1036
|
+
options.skipBuild === true || env?.AGENC_FROM_SOURCE_SKIP_BUILD === "1";
|
|
1037
|
+
|
|
1038
|
+
if (!skipBuild) {
|
|
1039
|
+
await runBuildScript(sourceRoot, env);
|
|
1040
|
+
}
|
|
1041
|
+
if (!existsSync(distRoot)) {
|
|
1042
|
+
throw new RuntimeInstallError(
|
|
1043
|
+
`from-source build did not produce ${distRoot}`,
|
|
1044
|
+
"from_source_dist_missing",
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const packageJson = await readJsonSafe(path.join(sourceRoot, "package.json"));
|
|
1049
|
+
const versionFile = await readBuildVersion(sourceRoot);
|
|
1050
|
+
const selectedArtifact = buildFromSourceArtifactDescriptor(
|
|
1051
|
+
sourceRoot,
|
|
1052
|
+
packageJson,
|
|
1053
|
+
versionFile,
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
const installPaths = await ensureFromSourceCurrentPointer({
|
|
1057
|
+
homeDir,
|
|
1058
|
+
distRoot,
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
releaseDir: installPaths.releaseDir,
|
|
1063
|
+
currentDir: installPaths.currentDir,
|
|
1064
|
+
runtimeHome: installPaths.runtimeHome,
|
|
1065
|
+
statePath: installPaths.statePath,
|
|
1066
|
+
manifestSource: "from-source",
|
|
1067
|
+
selectedArtifact,
|
|
1068
|
+
fromSource: true,
|
|
1069
|
+
sourceRoot,
|
|
1070
|
+
versionFile,
|
|
1071
|
+
bins: {
|
|
1072
|
+
agenc: path.join(installPaths.currentDir, selectedArtifact.bins.agenc),
|
|
1073
|
+
"agenc-runtime": path.join(
|
|
1074
|
+
installPaths.currentDir,
|
|
1075
|
+
selectedArtifact.bins["agenc-runtime"],
|
|
1076
|
+
),
|
|
1077
|
+
daemon: path.join(installPaths.currentDir, selectedArtifact.bins.daemon),
|
|
1078
|
+
watch: path.join(
|
|
1079
|
+
installPaths.currentDir,
|
|
1080
|
+
selectedArtifact.bins["agenc-watch"],
|
|
1081
|
+
),
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export async function spawnInstalledRuntimeBinFromSource(
|
|
1087
|
+
binName,
|
|
1088
|
+
args,
|
|
1089
|
+
options = {},
|
|
1090
|
+
) {
|
|
1091
|
+
const runtime = await ensureRuntimeFromSource(options);
|
|
1092
|
+
const scriptPath = runtime.bins[binName];
|
|
1093
|
+
if (!scriptPath) {
|
|
1094
|
+
throw new RuntimeInstallError(
|
|
1095
|
+
`from-source runtime does not expose ${binName}`,
|
|
1096
|
+
"missing_runtime_bin",
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const env = {
|
|
1100
|
+
...(options.env ?? process.env),
|
|
1101
|
+
AGENC_WRAPPER_ACTIVE: "1",
|
|
1102
|
+
AGENC_WRAPPER_RUNTIME_HOME: runtime.runtimeHome,
|
|
1103
|
+
AGENC_WRAPPER_RELEASE_DIR: runtime.releaseDir,
|
|
1104
|
+
AGENC_WRAPPER_CURRENT_DIR: runtime.currentDir,
|
|
1105
|
+
AGENC_WRAPPER_FROM_SOURCE: "1",
|
|
1106
|
+
AGENC_WRAPPER_SOURCE_ROOT: runtime.sourceRoot,
|
|
1107
|
+
AGENC_DAEMON_ENTRY: runtime.bins.daemon,
|
|
1108
|
+
AGENC_WATCH_ENTRY: runtime.bins.watch,
|
|
1109
|
+
};
|
|
1110
|
+
return spawnNodeScript(scriptPath, args, env, options.cwd);
|
|
1111
|
+
}
|