@ericsanchezok/synergy-presets 0.0.0-stage → 3.1.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.
Files changed (49) hide show
  1. package/AGENTS.md +30 -0
  2. package/README.md +74 -2
  3. package/dist/modules/access/index.js +21 -0
  4. package/dist/modules/cli/data/index.js +31 -0
  5. package/dist/modules/cli/data/merge.js +251 -0
  6. package/dist/modules/cli/data/move.js +300 -0
  7. package/dist/modules/cli/data/pack.js +130 -0
  8. package/dist/modules/cli/data/transfer.js +251 -0
  9. package/dist/modules/cli/data/workspace-transfer.js +215 -0
  10. package/dist/modules/cli/generate.js +1 -0
  11. package/dist/modules/cli/server.js +3 -0
  12. package/dist/modules/cli/web.js +1 -0
  13. package/dist/modules/cli-adapter.js +10 -0
  14. package/dist/modules/cli-commands.js +8 -0
  15. package/dist/modules/component.js +19 -0
  16. package/dist/modules/components.js +62 -0
  17. package/dist/modules/configuration.js +38 -0
  18. package/dist/modules/daemon-entry.js +19 -0
  19. package/dist/modules/features.js +4 -0
  20. package/dist/modules/index.js +39 -0
  21. package/dist/modules/registration.js +15 -0
  22. package/dist/modules/server/routes.js +13 -0
  23. package/dist/modules/server/runtime-handle.js +42 -0
  24. package/dist/modules/server/runtime.js +22 -0
  25. package/dist/modules/server/web-app.js +7 -0
  26. package/package.json +188 -4
  27. package/src/access/index.ts +23 -0
  28. package/src/cli/data/index.ts +34 -0
  29. package/src/cli/data/merge.ts +334 -0
  30. package/src/cli/data/move.ts +369 -0
  31. package/src/cli/data/pack.ts +153 -0
  32. package/src/cli/data/transfer.ts +273 -0
  33. package/src/cli/data/workspace-transfer.ts +234 -0
  34. package/src/cli/generate.ts +1 -0
  35. package/src/cli/server.ts +4 -0
  36. package/src/cli/web.ts +1 -0
  37. package/src/cli-adapter.ts +14 -0
  38. package/src/cli-commands.ts +9 -0
  39. package/src/component.ts +21 -0
  40. package/src/components.ts +65 -0
  41. package/src/configuration.ts +38 -0
  42. package/src/daemon-entry.ts +21 -0
  43. package/src/features.ts +4 -0
  44. package/src/index.ts +45 -0
  45. package/src/registration.ts +17 -0
  46. package/src/server/routes.ts +12 -0
  47. package/src/server/runtime-handle.ts +46 -0
  48. package/src/server/runtime.ts +30 -0
  49. package/src/server/web-app.ts +8 -0
package/AGENTS.md ADDED
@@ -0,0 +1,30 @@
1
+ # Presets Package
2
+
3
+ Own complete-product source composition, cross-domain data commands and integration fixtures. Generic Bun bootstrap belongs to Agent Runtime; CLI process lifecycle belongs to CLI, and HTTP asset serving belongs to Server. Read the root [AGENTS.md](../../AGENTS.md) and [architecture ownership map](../../docs/architecture/README.md#ownership-map) before changes.
4
+
5
+ - Assemble configuration, session schemas, migrations, tools, agents, routes and services explicitly before opening the shared Harness lifecycle through `PresetRuntimeHandle`. Startup seals configuration and migration registration; late imports must not partially install a capability.
6
+ - Keep business implementations with their owning packages. Presets connects typed sources and lifecycle contributions; do not move domain algorithms into assembly or introduce package scanning.
7
+ - Preserve the single `runCli()` parser and `synergy` command. Compose the same `createRuntimeCli` component adapters as an installed host. Full-data pack, merge and move operations belong here; Library owns its SQLite merge algorithms.
8
+ - Source and compiled agent workers use Agent Runtime role entrypoints and the explicitly pinned component plan; never register the full foreground composition in workers. Preserve resource discovery, verified sandbox assets, cancellation, evidence failures and shutdown drainage.
9
+ - Keep core-only tests in Harness or Local Runtime. Full API, cross-domain lifecycle and product configuration tests belong here and explicitly register their required services.
10
+ - Preserve unloaded domain data and existing migration IDs and ledgers. Read [persistence guidance](../../.synergy/skill/change-persistence/SKILL.md) before changing upgrades, imports or data movement.
11
+ - Full-data copies hold native directory retirement claims as well as offline Home locks, including selected folders and source removal. Copied Git relationship repair belongs to Local Runtime and must never reconnect to an uncopied source.
12
+ - Select Browser hosting only in the Desktop composition; CLI/Web omit it. Browser backend ownership belongs to `packages/browser-runtime`; protocol contracts belong to `packages/browser-core`.
13
+
14
+ Prepare native PTY assets with `bun ../../packages/local-runtime/script/build-pty.ts` before `bun test test/server/runtime-websocket-shutdown.test.ts`. CI supplies the verified native build artifact.
15
+
16
+ Run `bun run typecheck`, affected tests and `bun run test:coverage` from this package. The shared testing orchestrator injects a positive isolated-home marker into every child; never use raw parallel coverage. Model fixtures belong to `packages/testing/fixtures/models-api.json`.
17
+
18
+ `script/acceptance.ts` owns opt-in local joint acceptance through `plan/run/resume/report`; its behavioral regressions live in `test/acceptance`. Keep real-provider calls outside CI, retain failed attempts and unknown accounting, and require independently verified evidence before reporting coverage.
19
+
20
+ Run manual startup and CLI checks with an isolated `SYNERGY_HOME` using `develop-synergy`. Never restart or modify the active instance. For releases, verify full assets and an installed tarball outside the repository; source resolution alone cannot prove the package is complete.
21
+
22
+ Review the owning Skill for CLI, persistence, execution, API, Browser or Channel changes. Regenerate API contracts with the root `./script/generate.ts` and command documentation with `bun script/gen/gen-cli-reference.ts` when those public contracts change.
23
+
24
+ `bun run build` compiles workspace modules into `dist/modules`. Use the package-owned `bun script/build.ts --single --skip-install` for a local executable; release tooling selects its target matrix through that same explicit binary entry.
25
+
26
+ Consume declarative core/full/Web/Desktop selections from Plugin Host’s public `installation/catalog` leaf. Full includes backend components, HTTP and authoring tools; Web adds the application payload and Desktop adds its signed shell. `dataManagement()` contributes cross-domain data commands through the same component API. Source development also selects the local Web assets explicitly.
27
+
28
+ Public `PresetRuntimeHandle` embeds the backend without repository paths. Callers explicitly select Web assets with `webAppDirectory`; only the source CLI selects repository assets. Publish the active configuration schema through the shared lifecycle instead of copying an executable-adjacent schema. Verify both API-only and explicit-Web embedding outside the repository.
29
+
30
+ Feature selection uses Util’s shared resolver before registration. Full contract generation explicitly includes all declarations. Offline maintenance loads dormant owner data contributions without runtime activation; follow [develop-feature](../../.synergy/skill/develop-feature/SKILL.md).
package/README.md CHANGED
@@ -1,3 +1,75 @@
1
- # Temporary Holding Version
1
+ # Synergy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The core runtime for [Synergy](https://github.com/SII-Holos/synergy) — an AI agent platform built by the Holos team.
4
+
5
+ ## What's in this package
6
+
7
+ This is the engine behind Synergy. It provides:
8
+
9
+ - **Server runtime** — a stateless server that clients connect to over HTTP
10
+ - **CLI** — `synergy start`, `synergy web`, `synergy send`, and operational commands for sessions, config, identity, and more
11
+ - **Agent system** — built-in configurable agents for coding, research, writing, orchestration, and review
12
+ - **Sessions** — persistent conversation management with recall and context
13
+ - **Tools** — file operations, search, process management, web access, and extensible tool definitions
14
+ - **MCP integration** — connect external tool ecosystems via the Model Context Protocol
15
+ - **Provider support** — multi-provider LLM integration (OpenAI, Anthropic, Google, and many others)
16
+ - **Config system** — layered configuration with global, project, and runtime scopes
17
+ - **Library** — long-term memory and knowledge infrastructure
18
+ - **Notes, Agenda, Channels** — persistent notes, scheduled automation, and messaging integrations
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @ericsanchezok/synergy
24
+ # or
25
+ bun add @ericsanchezok/synergy
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ Start the background service:
31
+
32
+ ```bash
33
+ synergy start
34
+ ```
35
+
36
+ Then open the web interface or send a one-off message:
37
+
38
+ ```bash
39
+ synergy web
40
+ synergy send "summarize this project"
41
+ ```
42
+
43
+ Check on the service at any time:
44
+
45
+ ```bash
46
+ synergy status
47
+ synergy logs
48
+ ```
49
+
50
+ ## Session worktrees
51
+
52
+ For Git-backed scopes, use the built-in `/worktree` session command to isolate work in a project-local Git worktree:
53
+
54
+ ```text
55
+ /worktree list
56
+ /worktree new add-rate-limit
57
+ /worktree enter add-rate-limit
58
+ /worktree status
59
+ /worktree leave
60
+ /worktree remove add-rate-limit --force
61
+ ```
62
+
63
+ Synergy-created worktrees live in `.synergy/worktrees/`, and metadata lives in `.synergy/worktrees/.registry/`. New worktrees can run project-local setup from `.synergy/worktree-setup.jsonc` and machine-local overrides from `.synergy/worktree-setup.local.jsonc`.
64
+
65
+ See the main repository README for the full worktree workflow, setup file format, and cleanup rules.
66
+
67
+ ## Documentation
68
+
69
+ Local architecture acceptance uses `bun run acceptance --help` from this package. `plan` requires explicit scenario IDs and an external private evidence directory; `run` uses frozen settings, `resume` leaves completed or interrupted attempts untouched unless a retry is explicitly named with a reason, and `report` independently checks recorded evidence. Live scenarios make real provider requests. See the [testing workflow](../../.synergy/skill/testing-guide/SKILL.md#local-joint-acceptance).
70
+
71
+ For full setup instructions, configuration, development workflow, and the complete package map, see the [main repository README](https://github.com/SII-Holos/synergy).
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,21 @@
1
+ export var Access;
2
+ (function (Access) {
3
+ function normalizeUrl(value) {
4
+ return value.replace(/\/+$/, "");
5
+ }
6
+ Access.normalizeUrl = normalizeUrl;
7
+ function fromServerUrl(serverUrl) {
8
+ const attachUrl = normalizeUrl(serverUrl);
9
+ return {
10
+ attachUrl,
11
+ };
12
+ }
13
+ Access.fromServerUrl = fromServerUrl;
14
+ function frontendEnv(serverUrl) {
15
+ const descriptor = fromServerUrl(serverUrl);
16
+ return {
17
+ VITE_SYNERGY_SERVER_URL: descriptor.attachUrl,
18
+ };
19
+ }
20
+ Access.frontendEnv = frontendEnv;
21
+ })(Access || (Access = {}));
@@ -0,0 +1,31 @@
1
+ import { cmd } from "@ericsanchezok/synergy-util/cli-command";
2
+ import { createDataCommand } from "@ericsanchezok/synergy-cli/cli/cmd/data";
3
+ import { DataMoveCommand } from "./move.js";
4
+ import { DataPackCommand } from "./pack.js";
5
+ import { DataMergeCommand } from "./merge.js";
6
+ export const commands = [DataMoveCommand, DataPackCommand, DataMergeCommand];
7
+ export const DataCommand = createDataCommand(commands);
8
+ /** Backward-compatible alias: `synergy migrate` → `synergy data move` */
9
+ export const MigrateCommand = cmd({
10
+ command: "migrate",
11
+ describe: "move synergy data to a new location (alias for 'data move')",
12
+ builder: (yargs) => yargs
13
+ .option("target", { type: "string", describe: "target directory path" })
14
+ .option("remove-original", {
15
+ type: "boolean",
16
+ default: false,
17
+ describe: "remove original data after successful move",
18
+ })
19
+ .option("dry-run", {
20
+ type: "boolean",
21
+ default: false,
22
+ describe: "show plan without executing",
23
+ }),
24
+ handler: async (args) => {
25
+ const target = args.target;
26
+ const removeOriginal = args.removeOriginal;
27
+ const dryRun = args.dryRun;
28
+ const { executeMove } = await import("@ericsanchezok/synergy-presets/cli/data/move");
29
+ await executeMove({ target, removeOriginal, dryRun });
30
+ },
31
+ });
@@ -0,0 +1,251 @@
1
+ import { logo } from "@ericsanchezok/synergy-cli/branding";
2
+ import { getLibraryInfo, resolveLibraryDB, mergeLibraryDB, } from "@ericsanchezok/synergy-library/cli/data";
3
+ import { DataTransfer } from "./transfer.js";
4
+ import fs from "fs/promises";
5
+ import path from "path";
6
+ import os from "os";
7
+ import * as prompts from "@clack/prompts";
8
+ import { cmd } from "@ericsanchezok/synergy-util/cli-command";
9
+ import { UI } from "@ericsanchezok/synergy-util/terminal";
10
+ import { archiveExclusions, CATEGORIES, scanCategories, formatSize, shortenPath, dirExists, copyDirSkipExisting, dataRoot, } from "@ericsanchezok/synergy-cli/cli/cmd/data/shared";
11
+ export const DataMergeCommand = cmd({
12
+ command: "merge <source>",
13
+ describe: "merge data from another synergy directory or zip archive",
14
+ builder: (yargs) => yargs.positional("source", {
15
+ type: "string",
16
+ describe: "source directory path, zip, or tar.gz archive",
17
+ demandOption: true,
18
+ }),
19
+ handler: async (args) => {
20
+ const sourceArg = args.source;
21
+ const targetRoot = dataRoot();
22
+ UI.empty();
23
+ UI.println(logo(" "));
24
+ UI.empty();
25
+ prompts.intro("Merge Synergy Data");
26
+ // Prepare source
27
+ const source = await prepareSource(sourceArg);
28
+ if (!source) {
29
+ prompts.outro("Merge aborted");
30
+ return;
31
+ }
32
+ try {
33
+ const srcCatStats = await scanCategories(source.dataDir);
34
+ const tgtCatStats = await scanCategories(targetRoot);
35
+ let srcTotal = 0;
36
+ for (const stats of srcCatStats.values())
37
+ srcTotal += stats.size;
38
+ prompts.log.info(`Source: ${shortenPath(source.path)} (${formatSize(srcTotal)})`);
39
+ prompts.log.info(`Target: ${shortenPath(targetRoot)} (current)`);
40
+ // Check library compatibility from manifest or direct scan
41
+ const srcLibrary = await resolveLibraryDB(source.dataDir);
42
+ const tgtLibrary = path.join(targetRoot, "data", "library.db");
43
+ let srcLibraryInfo = await getLibraryInfo(srcLibrary);
44
+ let libraryStrategy = "text_only";
45
+ // If we have a manifest, show it
46
+ if (source.manifest) {
47
+ const m = source.manifest;
48
+ prompts.log.info(`Archive created: ${m.createdAt}`);
49
+ const manifestLibrary = m.library ?? m.engram;
50
+ if (manifestLibrary) {
51
+ prompts.log.info(`Source library: ${manifestLibrary.dimensions ?? "no"} dimensions${manifestLibrary.embeddingModel ? ` (${manifestLibrary.embeddingModel})` : ""}, ${manifestLibrary.memoryCount} memories`);
52
+ }
53
+ }
54
+ // Step 1: Select categories
55
+ const selectable = CATEGORIES.filter((c) => !c.required);
56
+ const selected = await prompts.multiselect({
57
+ message: "What should be merged? (Space to toggle, Enter to confirm)",
58
+ options: selectable.map((cat) => ({
59
+ value: cat.key,
60
+ label: cat.label,
61
+ hint: formatSize(srcCatStats.get(cat.key)?.size ?? 0),
62
+ })),
63
+ initialValues: selectable.filter((c) => c.defaultValue).map((c) => c.key),
64
+ required: false,
65
+ });
66
+ if (prompts.isCancel(selected)) {
67
+ prompts.cancel("Cancelled");
68
+ return;
69
+ }
70
+ const selectedKeys = new Set([
71
+ ...selected,
72
+ ...CATEGORIES.filter((c) => c.required).map((c) => c.key),
73
+ ]);
74
+ const selectedCategories = CATEGORIES.filter((c) => selectedKeys.has(c.key));
75
+ // Step 2: Confirm
76
+ const confirm = await prompts.confirm({
77
+ message: `Merge data into ${shortenPath(targetRoot)}?`,
78
+ initialValue: true,
79
+ });
80
+ if (confirm !== true || prompts.isCancel(confirm)) {
81
+ prompts.cancel("Cancelled");
82
+ return;
83
+ }
84
+ // Step 3: Handle library dimension mismatch
85
+ if (selectedKeys.has("core") && srcLibraryInfo.exists) {
86
+ const tgtLibraryInfo = await getLibraryInfo(tgtLibrary);
87
+ if (tgtLibraryInfo.exists &&
88
+ srcLibraryInfo.dimensions &&
89
+ tgtLibraryInfo.dimensions &&
90
+ srcLibraryInfo.dimensions !== tgtLibraryInfo.dimensions) {
91
+ prompts.log.warn("Vector dimension mismatch:");
92
+ prompts.log.info(` Source: ${srcLibraryInfo.dimensions}d${srcLibraryInfo.embeddingModel ? ` (${srcLibraryInfo.embeddingModel})` : ""}`);
93
+ prompts.log.info(` Target: ${tgtLibraryInfo.dimensions}d${tgtLibraryInfo.embeddingModel ? ` (${tgtLibraryInfo.embeddingModel})` : ""}`);
94
+ const choice = await prompts.select({
95
+ message: "How should library data be handled?",
96
+ options: [
97
+ {
98
+ value: "text_only",
99
+ label: "Merge text only, discard source vectors",
100
+ hint: "Source memories added without vector search until re-embedded",
101
+ },
102
+ { value: "skip", label: "Skip library entirely", hint: "Source memories are not imported" },
103
+ {
104
+ value: "replace_vectors",
105
+ label: "Replace: use source vectors, drop target vectors",
106
+ hint: "Your existing memories lose vector search",
107
+ },
108
+ ],
109
+ });
110
+ if (prompts.isCancel(choice)) {
111
+ prompts.cancel("Cancelled");
112
+ return;
113
+ }
114
+ libraryStrategy = choice;
115
+ }
116
+ else {
117
+ libraryStrategy = "text_only";
118
+ }
119
+ }
120
+ await using homes = await DataTransfer.lockHomes([source.dataDir, targetRoot]);
121
+ // Step 4: Execute merge
122
+ UI.empty();
123
+ const errors = [];
124
+ let libraryMerged = false;
125
+ for (const cat of selectedCategories) {
126
+ for (const subdir of cat.subdirs) {
127
+ const src = path.join(source.dataDir, subdir);
128
+ const dst = path.join(targetRoot, subdir);
129
+ if (!(await dirExists(src)))
130
+ continue;
131
+ // Special: library.db merge
132
+ if (subdir === "data" && libraryStrategy !== "skip" && !libraryMerged) {
133
+ const targetLibraryExists = await dirExists(tgtLibrary);
134
+ if (targetLibraryExists && srcLibraryInfo.exists) {
135
+ const librarySpinner = prompts.spinner();
136
+ librarySpinner.start("Merging library.db...");
137
+ try {
138
+ const result = await mergeLibraryDB(srcLibrary, tgtLibrary, libraryStrategy);
139
+ librarySpinner.stop(`Library: ${result.memoriesMerged} memories merged, ${result.experiencesMerged} experiences merged${result.memoriesSkipped > 0 ? `, ${result.memoriesSkipped} duplicates skipped` : ""}`);
140
+ libraryMerged = true;
141
+ }
142
+ catch (e) {
143
+ librarySpinner.error("Failed to merge library.db");
144
+ errors.push(`library.db: ${e instanceof Error ? e.message : String(e)}`);
145
+ }
146
+ }
147
+ }
148
+ const spinner = prompts.spinner();
149
+ spinner.start(`Merging ${subdir}/...`);
150
+ try {
151
+ const result = subdir === "data"
152
+ ? await DataTransfer.merge(source.dataDir, targetRoot)
153
+ : await copyDirSkipExisting(src, dst, (p) => {
154
+ const pct = Math.round(((p.copied + p.skipped) / p.total) * 100);
155
+ spinner.message(`Merging ${subdir}/ ${pct}% — ${shortenPath(p.currentFile)}`);
156
+ }, undefined, undefined, archiveExclusions(subdir));
157
+ const skippedNote = result.skipped > 0 ? ` (${result.skipped} existing files kept)` : "";
158
+ spinner.stop(`Merged ${subdir}/${skippedNote}`);
159
+ }
160
+ catch (e) {
161
+ spinner.error(`Failed to merge ${subdir}/`);
162
+ errors.push(`${subdir}: ${e instanceof Error ? e.message : String(e)}`);
163
+ }
164
+ }
165
+ }
166
+ // Report
167
+ UI.empty();
168
+ if (errors.length > 0) {
169
+ process.exitCode = 1;
170
+ prompts.log.warn("Merge completed with errors:");
171
+ for (const err of errors)
172
+ prompts.log.error(` ${err}`);
173
+ }
174
+ else {
175
+ prompts.log.success("Merge complete");
176
+ }
177
+ prompts.outro("Done");
178
+ }
179
+ finally {
180
+ await source.cleanup?.();
181
+ }
182
+ },
183
+ });
184
+ async function prepareSource(sourceArg) {
185
+ const resolved = path.resolve(sourceArg);
186
+ if (resolved.endsWith(".zip") || resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz")) {
187
+ if (!(await dirExists(resolved))) {
188
+ prompts.log.error(`File not found: ${shortenPath(resolved)}`);
189
+ return null;
190
+ }
191
+ const spinner = prompts.spinner();
192
+ spinner.start("Extracting archive...");
193
+ try {
194
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "synergy-merge-"));
195
+ const command = resolved.endsWith(".zip")
196
+ ? ["unzip", "-o", "-q", resolved, "-d", tmpDir]
197
+ : ["tar", "-xzf", resolved, "-C", tmpDir];
198
+ const child = Bun.spawn(command, { stdout: "ignore", stderr: "pipe" });
199
+ const errors = new Response(child.stderr).text();
200
+ if ((await child.exited) !== 0) {
201
+ await fs.rm(tmpDir, { recursive: true, force: true });
202
+ throw new Error(await errors);
203
+ }
204
+ await errors;
205
+ // Read manifest if it exists
206
+ const manifestPath = path.join(tmpDir, "manifest.json");
207
+ let manifest = null;
208
+ if (await Bun.file(manifestPath)
209
+ .exists()
210
+ .catch(() => false)) {
211
+ manifest = await Bun.file(manifestPath)
212
+ .json()
213
+ .catch(() => null);
214
+ }
215
+ spinner.stop("Archive extracted");
216
+ return {
217
+ type: "archive",
218
+ path: resolved,
219
+ dataDir: tmpDir,
220
+ manifest,
221
+ cleanup: () => fs.rm(tmpDir, { recursive: true, force: true }),
222
+ };
223
+ }
224
+ catch (e) {
225
+ spinner.error("Failed to extract archive");
226
+ prompts.log.error(`Extraction failed: ${e instanceof Error ? e.message : String(e)}`);
227
+ return null;
228
+ }
229
+ }
230
+ // Directory source
231
+ if (!(await dirExists(resolved))) {
232
+ prompts.log.error(`Directory not found: ${shortenPath(resolved)}`);
233
+ return null;
234
+ }
235
+ // Check for manifest (if merging from a previously packed+extracted dir)
236
+ const manifestPath = path.join(resolved, "manifest.json");
237
+ let manifest = null;
238
+ if (await Bun.file(manifestPath)
239
+ .exists()
240
+ .catch(() => false)) {
241
+ manifest = await Bun.file(manifestPath)
242
+ .json()
243
+ .catch(() => null);
244
+ }
245
+ return {
246
+ type: "directory",
247
+ path: resolved,
248
+ dataDir: resolved,
249
+ manifest,
250
+ };
251
+ }