From 21684912c20feaf1d86a84e5ab8199dc205f32cc Mon Sep 17 00:00:00 2001 From: Yash Date: Sat, 5 Sep 2026 12:12:00 +0000 Subject: [PATCH] feat(harness): activate ordinary bootstrap [Agent Map 07/15] --- .changeset/project-bootstrap-activation.md | 14 + .../scripts/smoke-agent-stub.cjs | 60 + packages/harness-desktop/scripts/smoke.sh | 37 +- .../src/main/smoke-agent-stub.test.ts | 60 + packages/harness-desktop/src/main/smoke.ts | 294 +++- packages/harness/README.md | 31 +- packages/harness/src/core/paths.test.ts | 6 - packages/harness/src/core/paths.ts | 4 +- .../harness/src/core/planner-greeting.test.ts | 962 ----------- packages/harness/src/core/planner-greeting.ts | 1489 ----------------- .../harness/src/core/planning-session.test.ts | 817 --------- packages/harness/src/core/planning-session.ts | 551 ------ .../harness/src/core/session-manager.test.ts | 1374 ++++++++++++--- packages/harness/src/core/session-manager.ts | 532 ++++-- .../harness/src/profiles/agent-map-planner.ts | 29 - .../src/server/agent-map-auth-wiring.test.ts | 28 +- .../src/server/agent-map-mcp-wiring.test.ts | 876 +++++++++- packages/harness/src/server/agent-map.test.ts | 340 ++-- packages/harness/src/server/agent-map.ts | 214 +-- packages/harness/src/server/auth-routes.ts | 9 +- packages/harness/src/server/index.ts | 1038 +++++++++--- packages/harness/src/server/ingest.test.ts | 29 +- .../server/project-bootstrap-outbox.test.ts | 547 ++++++ .../src/server/rehydrate-session.test.ts | 126 +- packages/harness/src/server/rest.test.ts | 591 +++++-- packages/harness/src/server/rest.ts | 211 ++- packages/harness/src/shared/agent-map.ts | 103 -- packages/harness/src/shared/types.ts | 47 +- 28 files changed, 4929 insertions(+), 5490 deletions(-) create mode 100644 .changeset/project-bootstrap-activation.md create mode 100644 packages/harness-desktop/scripts/smoke-agent-stub.cjs create mode 100644 packages/harness-desktop/src/main/smoke-agent-stub.test.ts delete mode 100644 packages/harness/src/core/planner-greeting.test.ts delete mode 100644 packages/harness/src/core/planner-greeting.ts delete mode 100644 packages/harness/src/core/planning-session.test.ts delete mode 100644 packages/harness/src/core/planning-session.ts delete mode 100644 packages/harness/src/profiles/agent-map-planner.ts create mode 100644 packages/harness/src/server/project-bootstrap-outbox.test.ts diff --git a/.changeset/project-bootstrap-activation.md b/.changeset/project-bootstrap-activation.md new file mode 100644 index 000000000..3cfad9f50 --- /dev/null +++ b/.changeset/project-bootstrap-activation.md @@ -0,0 +1,14 @@ +--- +"@sapiom/harness": minor +--- + +Activate one recoverable first-session map bootstrap for newly opened Studio projects. Durable project intent, input receipts, readiness, preemption, and restart recovery share the ordinary session lifecycle. + +Breaking changes for embedders and HTTP clients: + +- Remove `HarnessSession.planning`, `SessionManager.setPlanningMetadata()`, and the trusted create/resume `planning` options. Read the neutral `agentMapIdentity` for project identity and optional `projectBootstrap` for lifecycle state. The server migrates valid persisted legacy metadata and input queues automatically. +- Remove the planner contracts `PlannerGreetingErrorCode`, `PlannerGreetingState`, `PlannerSessionMetadata`, `PlannerQueuedInput`, `PlannerSessionRequest`, `PlannerSessionResponse`, `PlannerMessageRequest`, `PlannerSessionMetadataResponse`, and `PlannerLifecycleEvent`. Use ordinary session request/response types and `ProjectBootstrapMetadata`, `ProjectBootstrapState`, and `ProjectBootstrapInputReceipt` for bootstrap state and input acknowledgements. +- Remove `POST /api/projects/:projectId/planner-sessions`, `POST /api/projects/:projectId/planner-sessions/:sessionId/messages`, and `POST /api/projects/:projectId/planner-sessions/:sessionId/greeting/retry`. Create, resume, and send input through `POST /api/sessions`, `POST /api/sessions/:id/resume`, and `POST /api/sessions/:id/input`. Bootstrap recovery is server-owned. Clients supplying the first prompt should set `initialUserInputPending: true` when creating a session. +- Replace the `planner_session.*` and `planner_greeting.*` analytics event names with `project_agent.identity_*` and `project_bootstrap.*`. Their remote projections remain content-free. + +Project creation and root binding can return `202` with a committed project identity when automatic initialization must retry; treat that identity as successfully created. Settings updates return their committed values while new-root initialization continues in the background. Server shutdown keeps admission fenced and bounds its wait for bootstrap, persistence, archive, and telemetry drains before releasing the listener. diff --git a/packages/harness-desktop/scripts/smoke-agent-stub.cjs b/packages/harness-desktop/scripts/smoke-agent-stub.cjs new file mode 100644 index 000000000..9c4facb66 --- /dev/null +++ b/packages/harness-desktop/scripts/smoke-agent-stub.cjs @@ -0,0 +1,60 @@ +const fs = require("node:fs"); + +function environmentCapturePath(base, sessionId) { + return `${base}.${encodeURIComponent(sessionId)}.json`; +} + +function captureAgentEnvironment(env = process.env) { + const base = env.SAPIOM_SMOKE_AGENT_ENV; + const sessionId = env.SAPIOM_HARNESS_SESSION_ID; + if (!base || !sessionId) return null; + + const file = environmentCapturePath(base, sessionId); + const snapshot = { + schemaVersion: 1, + sessionId, + variableCount: Object.keys(env).length, + hasEsbuildBinaryPath: Object.prototype.hasOwnProperty.call( + env, + "ESBUILD_BINARY_PATH", + ), + hasPath: typeof env.PATH === "string" && env.PATH.length > 0, + }; + fs.writeFileSync(file, `${JSON.stringify(snapshot)}\n`, { + encoding: "utf8", + mode: 0o600, + }); + return file; +} + +function runSessionStartHook() { + try { + const settingsIndex = process.argv.indexOf("--settings"); + const settingsPath = + settingsIndex > -1 ? process.argv[settingsIndex + 1] : null; + if (!settingsPath) return; + const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); + const command = settings.hooks.SessionStart[0].hooks[0].command; + const { execFileSync, execSync } = require("node:child_process"); + if (process.platform === "win32") { + const bash = "C:\\Program Files\\Git\\bin\\bash.exe"; + if (fs.existsSync(bash)) { + execFileSync(bash, ["-c", command], { stdio: "ignore" }); + } else { + execSync(command, { stdio: "ignore" }); + } + } else { + execFileSync("/bin/sh", ["-c", command], { stdio: "ignore" }); + } + } catch { + // A failed hook is exactly what checkSessionCreate's ready poll reports. + } +} + +module.exports = { captureAgentEnvironment, environmentCapturePath }; + +if (require.main === module) { + captureAgentEnvironment(); + runSessionStartHook(); + setTimeout(() => process.exit(0), 3000); +} diff --git a/packages/harness-desktop/scripts/smoke.sh b/packages/harness-desktop/scripts/smoke.sh index 8ce6e15f6..2a5a6c790 100755 --- a/packages/harness-desktop/scripts/smoke.sh +++ b/packages/harness-desktop/scripts/smoke.sh @@ -75,52 +75,31 @@ export SAPIOM_SMOKE_OUT="$(native "$report_file")" # check can assert the WHOLE readiness chain (settings → hook command → node # resolution under the hook shell → POST → ready), the exact seam that broke # silently on Windows and dropped every held first prompt. -cat > "$smoke_home/stub-agent.js" <<'STUBJS' -const fs = require("fs"); -const envFile = process.env.SAPIOM_SMOKE_AGENT_ENV; -if (envFile) fs.writeFileSync(envFile, Object.entries(process.env).map(([k, v]) => k + "=" + v).join("\n") + "\n"); -try { - const i = process.argv.indexOf("--settings"); - const settingsPath = i > -1 ? process.argv[i + 1] : null; - if (settingsPath) { - const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); - const command = settings.hooks.SessionStart[0].hooks[0].command; - const { execFileSync, execSync } = require("child_process"); - if (process.platform === "win32") { - const bash = "C:\\Program Files\\Git\\bin\\bash.exe"; - if (fs.existsSync(bash)) execFileSync(bash, ["-c", command], { stdio: "ignore" }); - else execSync(command, { stdio: "ignore" }); - } else { - execFileSync("/bin/sh", ["-c", command], { stdio: "ignore" }); - } - } -} catch { - // A failed hook is exactly what the ready-poll in checkSessionCreate reports. -} -setTimeout(() => process.exit(0), 3000); -STUBJS +cp "$here/smoke-agent-stub.cjs" "$smoke_home/stub-agent.cjs" if [ "$(uname -s)" != "Linux" ] && [ "$(uname -s)" != "Darwin" ]; then # Shaped like an npm shim on purpose — a `.cmd` that runs `node