@foldspace_npm/harness 0.1.29 → 0.1.30
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/CLAUDE.md +13 -4
- package/README.md +1 -1
- package/bin/build.ts +99 -0
- package/package.json +1 -1
- package/src/cli-registry.mjs +2 -0
- package/src/runtime/VERSION +1 -0
package/CLAUDE.md
CHANGED
|
@@ -696,6 +696,8 @@ Each of these cost a real build.
|
|
|
696
696
|
`AUTH_SOURCE` and `LOAD_MODE`
|
|
697
697
|
- `agent/utils.ts` — configures the harness runtime and re-exports it. **The
|
|
698
698
|
only import surface for actions.**
|
|
699
|
+
- `agent/runtime/` — a code project only: this harness's runtime, which every
|
|
700
|
+
build keeps current. Never edit it.
|
|
699
701
|
- `agent/views/` — branded cards
|
|
700
702
|
- `docs/app-profile.md` — what is known about this app, and how it was established
|
|
701
703
|
- `foldspace.dev.json` — local harness target configuration
|
|
@@ -766,10 +768,17 @@ README's **Adapt it** table lists to what you observed.
|
|
|
766
768
|
package: Foldspace builds the published bundle with no `node_modules`, so a
|
|
767
769
|
package import passes every gate in this manual and then fails on publish.
|
|
768
770
|
Import the helpers the way the project's own `agent/utils.ts` does - from
|
|
769
|
-
`./runtime` - in a recipe file you copy too.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
771
|
+
`./runtime` - in a recipe file you copy too. `agent/runtime/` is this harness's
|
|
772
|
+
runtime, copied in for that reason. A build that changes files there names
|
|
773
|
+
them: save exactly those files in a save of their own as soon as the agent is
|
|
774
|
+
on their page. Say you are bringing their project up to date - no need to ask,
|
|
775
|
+
it changes nothing for their users. If their account cannot save, carry on.
|
|
776
|
+
Never edit a file there: the next build puts the harness's copy back. To change
|
|
777
|
+
how a helper behaves, define it in `agent/utils.ts` and export it from there in
|
|
778
|
+
place of the runtime's. A project with an `agent/utils/` folder has its own
|
|
779
|
+
helpers instead: change the sign-in there, and never add an `agent/utils.ts`
|
|
780
|
+
beside it, which would win the import. Without a code project, the package
|
|
781
|
+
import is right.
|
|
773
782
|
|
|
774
783
|
## Safety
|
|
775
784
|
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ constraint this package exists to protect.
|
|
|
28
28
|
- `agent/actions/*` and `agent/api/*` — a handler is `fetch` plus `runTask`,
|
|
29
29
|
nothing more. Actions built in one track run unmodified in the other.
|
|
30
30
|
- `agent/utils.ts` — configures and re-exports `@foldspace_npm/harness/runtime`
|
|
31
|
-
- `foldspace build` — esbuild → `dist/index.js`
|
|
31
|
+
- `foldspace build` — esbuild → `dist/index.js`. In a code project, it first makes `agent/runtime/` match this harness's runtime (never rolling back a copy of a newer runtime; the version is in `src/runtime/VERSION`) and names every file it wrote
|
|
32
32
|
- `foldspace deploy` (Foldspace-internal) - uploads `dist/index.js` to `agent/actions/<env>/<productId>/<agentApiName>`
|
|
33
33
|
- fixtures and tests
|
|
34
34
|
- **the verb interface** below
|
package/bin/build.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { bundleTurnsOnTestMode, lintProject, printLint } from "../src/lint/index.mjs";
|
|
6
|
+
import { compareVersions } from "../src/upgrade.mjs";
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const projectDir = process.env.FOLDSPACE_PROJECT_DIR || process.cwd();
|
|
@@ -11,6 +12,100 @@ const agentActionsEntry = path.join(projectDir, "agent", "actions", "index.ts");
|
|
|
11
12
|
|
|
12
13
|
const watchMode = process.argv.includes("--watch");
|
|
13
14
|
|
|
15
|
+
// A code project carries its own copy of this harness's runtime in
|
|
16
|
+
// agent/runtime/, because Foldspace builds it with no node_modules. Each build
|
|
17
|
+
// keeps that copy the same as the runtime here, so the harness's fixes reach
|
|
18
|
+
// the project, and names what it wrote so the session saves it.
|
|
19
|
+
// src/runtime/VERSION is the runtime's own version - it changes only when the
|
|
20
|
+
// runtime does - and the copy carries it in agent/runtime/VERSION: a copy of a
|
|
21
|
+
// newer runtime is left alone, so an older harness never rolls it back.
|
|
22
|
+
// Other files there, and links, are left alone too.
|
|
23
|
+
let runtimeSaveLine = "";
|
|
24
|
+
|
|
25
|
+
function plainFile(file: string): "missing" | "file" | "other" {
|
|
26
|
+
const stat = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
27
|
+
if (!stat) return "missing";
|
|
28
|
+
return stat.isFile() ? "file" : "other";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sameText(left: string, right: string): boolean {
|
|
32
|
+
return left.replace(/\r\n/g, "\n") === right.replace(/\r\n/g, "\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function refreshRuntimeCopy(): void {
|
|
36
|
+
const copyDir = path.join(projectDir, "agent", "runtime");
|
|
37
|
+
const copyStat = fs.lstatSync(copyDir, { throwIfNoEntry: false });
|
|
38
|
+
if (!copyStat) return;
|
|
39
|
+
if (copyStat.isSymbolicLink()) {
|
|
40
|
+
console.log("agent/runtime/ is a link, so this build leaves it as is.\n");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!copyStat.isDirectory()) return;
|
|
44
|
+
|
|
45
|
+
const sourceDir = path.join(__dirname, "..", "src", "runtime");
|
|
46
|
+
const runtimeVersion = fs.readFileSync(path.join(sourceDir, "VERSION"), "utf8").trim();
|
|
47
|
+
const versionFile = path.join(copyDir, "VERSION");
|
|
48
|
+
const versionKind = plainFile(versionFile);
|
|
49
|
+
if (versionKind === "other") {
|
|
50
|
+
console.log("agent/runtime/VERSION is not a plain file, so this build leaves agent/runtime/ as is.\n");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const copyVersion = versionKind === "file" ? fs.readFileSync(versionFile, "utf8").trim() : "";
|
|
54
|
+
if (copyVersion && compareVersions(copyVersion, runtimeVersion) > 0) {
|
|
55
|
+
console.log(
|
|
56
|
+
`agent/runtime/ is runtime ${copyVersion}, newer than this harness's (${runtimeVersion}), so this build uses it as it is. ` +
|
|
57
|
+
"Unless the human already said no to upgrading this session, ask them, then run npx foldspace upgrade --yes and build again.\n",
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const toWrite: Array<[string, string]> = [];
|
|
63
|
+
for (const name of fs.readdirSync(sourceDir).filter((file) => file.endsWith(".ts")).sort()) {
|
|
64
|
+
const source = fs.readFileSync(path.join(sourceDir, name), "utf8");
|
|
65
|
+
const target = path.join(copyDir, name);
|
|
66
|
+
const kind = plainFile(target);
|
|
67
|
+
if (kind === "other") continue;
|
|
68
|
+
if (kind === "file" && sameText(fs.readFileSync(target, "utf8"), source)) continue;
|
|
69
|
+
toWrite.push([name, source]);
|
|
70
|
+
}
|
|
71
|
+
const stamp = copyVersion !== runtimeVersion;
|
|
72
|
+
if (toWrite.length === 0 && !stamp) return;
|
|
73
|
+
|
|
74
|
+
const targets = [...toWrite.map(([name]) => path.join(copyDir, name)), ...(stamp ? [versionFile] : [])];
|
|
75
|
+
const blocked = targets.filter((file) => {
|
|
76
|
+
try {
|
|
77
|
+
fs.accessSync(fs.existsSync(file) ? file : copyDir, fs.constants.W_OK);
|
|
78
|
+
return false;
|
|
79
|
+
} catch {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const named = (files: string[]) => files.map((file) => `agent/runtime/${path.basename(file)}`).join(", ");
|
|
84
|
+
if (blocked.length > 0) {
|
|
85
|
+
console.error(`Could not update agent/runtime/: ${named(blocked)} cannot be written. Fix that and build again.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
for (const [name, source] of toWrite) fs.writeFileSync(path.join(copyDir, name), source);
|
|
90
|
+
if (stamp) fs.writeFileSync(versionFile, `${runtimeVersion}\n`);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`Could not update agent/runtime/: ${(error as Error).message}. Fix that and build again.`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The same runtime version that no longer matches was changed after it was copied.
|
|
97
|
+
if (!stamp) {
|
|
98
|
+
console.log(
|
|
99
|
+
`${named(toWrite.map(([name]) => name))} did not match this harness's copy of the same runtime; this build put the harness's copy back. ` +
|
|
100
|
+
"Never edit agent/runtime/ - to change how a helper behaves, define it in agent/utils.ts and export it from there in place of the runtime's.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
runtimeSaveLine =
|
|
104
|
+
`Updated to this harness's runtime (${runtimeVersion}): ${named(targets)}. ` +
|
|
105
|
+
"Save exactly these files to the code project in a save of their own, as soon as the agent is on their page - saving changes nothing for their users.";
|
|
106
|
+
console.log(`${runtimeSaveLine}\n`);
|
|
107
|
+
}
|
|
108
|
+
|
|
14
109
|
function gateLint(): void {
|
|
15
110
|
const result = lintProject(projectDir);
|
|
16
111
|
printLint(result);
|
|
@@ -75,6 +170,8 @@ async function build() {
|
|
|
75
170
|
process.exit(1);
|
|
76
171
|
}
|
|
77
172
|
|
|
173
|
+
refreshRuntimeCopy();
|
|
174
|
+
|
|
78
175
|
if (!watchMode) {
|
|
79
176
|
gateLint();
|
|
80
177
|
}
|
|
@@ -105,6 +202,8 @@ async function build() {
|
|
|
105
202
|
if (!bundleIsSafe()) process.exit(1);
|
|
106
203
|
console.log("Built: dist/index.js");
|
|
107
204
|
console.log("\nBuild complete!");
|
|
205
|
+
// Said again at the end, where a session reading only the last lines sees it.
|
|
206
|
+
if (runtimeSaveLine) console.log(`\n${runtimeSaveLine}`);
|
|
108
207
|
}
|
|
109
208
|
} catch (error) {
|
|
110
209
|
console.error("Build failed:", error);
|
package/package.json
CHANGED
package/src/cli-registry.mjs
CHANGED
|
@@ -129,11 +129,13 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
129
129
|
prerequisites: ["agent/actions/index.ts"],
|
|
130
130
|
effects: [
|
|
131
131
|
"Checks the harness version first: behind prints one line, a withdrawn version refuses to run (FOLDSPACE_SKIP_UPDATE_CHECK=1 skips the check)",
|
|
132
|
+
"In a code project (agent/runtime/ present), first makes each agent/runtime/*.ts match this harness's runtime and records the runtime's version in agent/runtime/VERSION, naming every file it wrote; a copy of a newer runtime, other files there and links are left alone",
|
|
132
133
|
"Runs foldspace lint first; errors skip bundling",
|
|
133
134
|
"Writes dist/index.js when lint has no errors",
|
|
134
135
|
],
|
|
135
136
|
next: [
|
|
136
137
|
"First build: `npm run inject` (the human signs in in that window), `npx foldspace observe wait-login`, then `npx foldspace attach --daemon`",
|
|
138
|
+
"If the build names files in agent/runtime/ to save: save exactly those to the code project, in a save of their own, once the agent is on their page",
|
|
137
139
|
"Rebuild while attach runs: `npx foldspace attach --stop`, then `npx foldspace attach --daemon` with the flags it ran with (`--bootstrap`, `--replace`, `--test-mode`) - a running attach keeps the actions it loaded",
|
|
138
140
|
],
|
|
139
141
|
}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.30
|