@foldspace_npm/harness 0.1.29 → 0.1.31
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 +28 -7
- 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/templates/agent-starter/AGENTS.md +24 -0
package/CLAUDE.md
CHANGED
|
@@ -29,8 +29,8 @@ session ends at something they can **see on their own page** - not at
|
|
|
29
29
|
|
|
30
30
|
| Result | Do |
|
|
31
31
|
|---|---|
|
|
32
|
-
| The tool does not exist | The Foldspace MCP is not installed in this editor. Install it yourself, the one way that fits *this* region and editor. **EU** - any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** - Claude Code with git on the machine (`command -v git`): `claude plugin marketplace add https://github.com/foldspace-ai/plugins` (already added, or added from another source, is fine), then `claude plugin install foldspace-agent-builder@foldspace-plugins`; without git, or any other editor: the MCP block on <https://foldspace.ai/agents.md>. Either way it needs sign-in: start it before your turn ends, as **Connect Foldspace tools** on that page says. **The plugin** loads when your turn ends: call `list_agents` again on the human's next message. **A server added from the MCP block** (`claude mcp add`) never reaches a chat that is already running - not on the next message, not through `/mcp`: once it is signed in, the human opens a new chat in this folder and repeats their request. `claude mcp get` saying Connected proves only the setting; the check is `list_agents` in the chat you are in |
|
|
33
|
-
| It exists but fails on auth | Installed, not signed in. Only the human can approve sign-in. Tell them exactly where - Claude Code: `/mcp` → **foldspace-eu** (**EU**) or **foldspace** (**US**; listed as `plugin:foldspace-agent-builder:foldspace` when the plugin installed it) → finish in the browser - then check again. If `/mcp` does not list that server, it was added after this chat started: the human opens a new chat in this folder, as above |
|
|
32
|
+
| The tool does not exist | The Foldspace MCP is not installed in this editor. Install it yourself, the one way that fits *this* region and editor. **EU** - any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** - Claude Code with git on the machine (`command -v git`): `claude plugin marketplace add https://github.com/foldspace-ai/plugins` (already added, or added from another source, is fine), then `claude plugin install foldspace-agent-builder@foldspace-plugins`; without git, or any other editor: the MCP block on <https://foldspace.ai/agents.md>. Either way it needs sign-in: start it before your turn ends, as **Connect Foldspace tools** on that page says. **The plugin** loads when your turn ends: call `list_agents` again on the human's next message. **A server added from the MCP block** (`claude mcp add`) never reaches a chat that is already running - not on the next message, not through `/mcp`: once it is signed in, the human opens a new chat in this folder and repeats their request. `claude mcp get` saying Connected proves only the setting; the check is `list_agents` in the chat you are in. **Codex**: `codex mcp add foldspace --url https://api.foldspace.ai/mcp` (**EU**: `foldspace-eu` and `https://api.eu.foldspace.ai/mcp`) adds it and starts sign-in in one step; a server added during a thread never reaches that thread, so once it is signed in the work continues in a new thread in this folder: start it yourself if Codex gives you a tool for it - the human's request word for word, plus the one paragraph the playbook's Codex block gives - and say so in one line; otherwise the human starts it and repeats their request. **Cursor**: merge the server into `~/.cursor/mcp.json` yourself, keeping every other server; it reaches this chat on the human's next message, needing sign-in. **Any editor**: never read a saved sign-in or credentials file, and never reach the Foldspace MCP any other way (curl, a script, a client you write). If its tools are not in this chat, the fix is a new chat |
|
|
33
|
+
| It exists but fails on auth | Installed, not signed in. Only the human can approve sign-in. Tell them exactly where - Claude Code: `/mcp` → **foldspace-eu** (**EU**) or **foldspace** (**US**; listed as `plugin:foldspace-agent-builder:foldspace` when the plugin installed it) → finish in the browser - then check again. If `/mcp` does not list that server, it was added after this chat started: the human opens a new chat in this folder, as above. Codex: run `codex mcp login foldspace` (`foldspace-eu` for **EU**) - if the browser does not open, give the human the link it prints - then a new thread. Cursor: call the server's own sign-in tool, `mcp_auth`, yourself - the human finishes in the browser - then `list_agents` in this same chat |
|
|
34
34
|
| It returns agents | One agent: state its name, Agent Key and product in a line and carry on; the human objects if it is wrong. Several: ask which, as a choice |
|
|
35
35
|
|
|
36
36
|
3. **Orient** — `get_agent_settings`, `list_actions`, `list_task_agents`,
|
|
@@ -162,7 +162,7 @@ Uploads and extraction are a later session.
|
|
|
162
162
|
### Ask as a choice, and ask almost nothing
|
|
163
163
|
|
|
164
164
|
Every question to the human is an **enumerated choice with the recommended
|
|
165
|
-
option first** (in Claude Code, `AskUserQuestion
|
|
165
|
+
option first** (in Claude Code, `AskUserQuestion`; elsewhere, the editor's own question tool), asked once - not repeated in your message. A first session asks only
|
|
166
166
|
this: which agent (only if several), the app's hostname, which experience
|
|
167
167
|
first - and a yes wherever this manual asks for one, such as agreeing the
|
|
168
168
|
experience (below), upgrading, publishing an action, task agent or
|
|
@@ -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
|
|
|
@@ -785,6 +794,18 @@ the package import is right.
|
|
|
785
794
|
`Bash(npx foldspace observe:*)` - and meanwhile hand that step to them: for
|
|
786
795
|
a chat turn, that is them typing the question into the panel. `init`,
|
|
787
796
|
`upgrade` and `deploy` ask on purpose: never give an allow rule for them.
|
|
797
|
+
- **A sandbox error is not a refusal.** Some editors (Codex) run your commands
|
|
798
|
+
in a sandbox: no network, and no writes outside this folder, unless the
|
|
799
|
+
human approves that one command. There `npm`, `npx foldspace`, `inject` and
|
|
800
|
+
anything that reaches the test window fail with errors that look like
|
|
801
|
+
something else - a host that cannot be resolved, `EPERM` in npm's cache,
|
|
802
|
+
`listen EPERM`, Chrome quitting, or *the test window is closed*. Ask to run
|
|
803
|
+
that same command outside the sandbox through the editor's own approval,
|
|
804
|
+
with one plain line on why; that approval is not a retry. Never move npm's
|
|
805
|
+
cache, change file ownership, or have the human run the command instead.
|
|
806
|
+
An approval can expire when the human sends a new message, so after one,
|
|
807
|
+
the same errors mean ask again - not that the window closed. If the human
|
|
808
|
+
declines, that is a refusal: the rule above applies.
|
|
788
809
|
- **Never create an account or a key** - not in Foldspace, not in the
|
|
789
810
|
customer's product, not anywhere. If one is missing, say which and where it
|
|
790
811
|
goes. **Never widen your own access**: if something is refused for a site or
|
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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Foldspace actions for {{APP_DOMAIN}}
|
|
2
|
+
|
|
3
|
+
This folder builds a Foldspace agent for {{APP_DOMAIN}}. Its manual is
|
|
4
|
+
`node_modules/@foldspace_npm/harness/CLAUDE.md` - written for every coding
|
|
5
|
+
agent, whatever the file name says.
|
|
6
|
+
|
|
7
|
+
**Read that manual in full before anything else**, and follow it from **Every
|
|
8
|
+
session starts the same way**. It is long: read it in parts of about 250 lines
|
|
9
|
+
until you reach its last line, because a tool that cuts long output short has
|
|
10
|
+
not shown you all of it. If the file is missing, run
|
|
11
|
+
`npm install --ignore-scripts` first. Run every command from this folder, even
|
|
12
|
+
when your chat started somewhere else. What follows is only what is true of
|
|
13
|
+
**this** product.
|
|
14
|
+
|
|
15
|
+
## This product
|
|
16
|
+
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Agent Key | `{{AGENT_API_NAME}}` |
|
|
20
|
+
| Product ID | `{{PRODUCT_ID}}` |
|
|
21
|
+
| App | `{{APP_DOMAIN}}` |
|
|
22
|
+
|
|
23
|
+
What is known about this app, and how each fact was established, is in
|
|
24
|
+
`docs/app-profile.md`.
|