@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 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. A project with an `agent/utils/`
770
- folder has its own helpers instead: change the sign-in there, and never add an
771
- `agent/utils.ts` beside it, which would win the import. Without a code project,
772
- the package import is right.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldspace_npm/harness",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Build and verify portable Foldspace action artifacts against a live app.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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