@venturewild/workspace 0.6.0 → 0.6.2

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 (56) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +112 -112
  3. package/package.json +85 -85
  4. package/server/bin/wild-workspace.mjs +1096 -1096
  5. package/server/src/account.mjs +114 -114
  6. package/server/src/agent-login.mjs +146 -146
  7. package/server/src/agent-readiness.mjs +200 -200
  8. package/server/src/agent.mjs +468 -468
  9. package/server/src/bazaar/core.mjs +974 -974
  10. package/server/src/bazaar/index.mjs +88 -88
  11. package/server/src/bazaar/mcp-server.mjs +429 -429
  12. package/server/src/bazaar/mock-tickup.mjs +97 -97
  13. package/server/src/bazaar/preview-server.mjs +95 -95
  14. package/server/src/bazaar/seed-recipes/customer-feedback-form/know-how.md +23 -23
  15. package/server/src/bazaar/seed-recipes/customer-feedback-form/recipe.json +24 -24
  16. package/server/src/bazaar/seed-recipes/landing-page-launch/know-how.md +29 -29
  17. package/server/src/bazaar/seed-recipes/landing-page-launch/recipe.json +25 -25
  18. package/server/src/bazaar/seed-recipes/personal-portfolio/know-how.md +21 -21
  19. package/server/src/bazaar/seed-recipes/personal-portfolio/recipe.json +24 -24
  20. package/server/src/bazaar/seed-recipes/receipt-sorter/know-how.md +31 -31
  21. package/server/src/bazaar/seed-recipes/receipt-sorter/recipe.json +25 -25
  22. package/server/src/bazaar/seed-recipes/tickup-hr-matching/know-how.md +79 -79
  23. package/server/src/bazaar/seed-recipes/tickup-hr-matching/recipe.json +40 -40
  24. package/server/src/canvas/core.mjs +446 -446
  25. package/server/src/canvas/index.mjs +42 -42
  26. package/server/src/canvas/mcp-server.mjs +253 -253
  27. package/server/src/canvas-rails.mjs +108 -108
  28. package/server/src/config.mjs +404 -404
  29. package/server/src/daemon-bin.mjs +110 -110
  30. package/server/src/daemon-supervisor.mjs +285 -285
  31. package/server/src/doctor.mjs +375 -375
  32. package/server/src/inbox.mjs +86 -86
  33. package/server/src/index.mjs +3332 -3322
  34. package/server/src/listings-rails.mjs +156 -156
  35. package/server/src/logpaths.mjs +98 -98
  36. package/server/src/observability.mjs +45 -45
  37. package/server/src/operator.mjs +92 -92
  38. package/server/src/pairing.mjs +137 -137
  39. package/server/src/service.mjs +515 -515
  40. package/server/src/session-reporter.mjs +201 -201
  41. package/server/src/settings.mjs +145 -145
  42. package/server/src/share.mjs +182 -182
  43. package/server/src/skills.mjs +213 -213
  44. package/server/src/supervisor.mjs +647 -647
  45. package/server/src/support-consent.mjs +133 -133
  46. package/server/src/sync.mjs +248 -248
  47. package/server/src/transcript.mjs +121 -121
  48. package/server/src/turn-mcp.mjs +46 -46
  49. package/server/src/usage.mjs +405 -405
  50. package/server/src/workspace-registry.mjs +295 -295
  51. package/server/src/workspaces.mjs +145 -145
  52. package/web/dist/assets/index-BgFan7ls.js +131 -0
  53. package/web/dist/assets/index-DHts78rO.css +32 -0
  54. package/web/dist/index.html +2 -2
  55. package/web/dist/assets/index-CSWkWdtM.js +0 -131
  56. package/web/dist/assets/index-CzUrGoMW.css +0 -32
@@ -1,121 +1,121 @@
1
- // TranscriptRecorder — the conversation *content* channel (separate from the
2
- // events/health feed in session-reporter.mjs).
3
- //
4
- // WHY a separate channel: per the locked design, the feed carries WHAT happened
5
- // (redacted, no words); the actual conversation is captured here and synced via
6
- // bmo-sync — modeled on the team's transcript-backup (Claude `Stop` hook →
7
- // jsonl→markdown → claude_backups/<user>/<date>/). bmo-sync REPLACES that Dropbox
8
- // transport: each user's transcript is appended to an account-scoped store, so we
9
- // avoid the "… conflicted copy …" litter a shared mutable file accrues (CLAUDE.md
10
- // principle #1 — and the exact failure observed in the team's Dropbox folder).
11
- //
12
- // HOW: taps the ActivityBus (which carries the raw turn text the wild-workspace
13
- // chat already streams), buffers turns to markdown, and APPENDS incrementally
14
- // (append-only, never a rewrite — no shared mutable doc) to
15
- // ~/.wild-workspace/transcripts/<workspaceId>/<date>.md — OUTSIDE the synced
16
- // repo. The local write is the user's own data (harmless); forwarding to us is
17
- // consent-gated (the caller supplies `forwardImpl` only when consent is on).
18
- // Debounced ~10 min like hook_trigger.py.
19
-
20
- import fs from 'node:fs';
21
- import path from 'node:path';
22
-
23
- const FLUSH_MS = 10 * 60 * 1000;
24
-
25
- export class TranscriptRecorder {
26
- constructor({
27
- dir,
28
- agentName = 'Agent',
29
- flushMs = FLUSH_MS,
30
- nowImpl = () => Date.now(),
31
- appendImpl,
32
- forwardImpl = null,
33
- } = {}) {
34
- this.dir = dir;
35
- this.agentName = agentName || 'Agent';
36
- this.flushMs = flushMs;
37
- this.nowImpl = nowImpl;
38
- this.appendImpl =
39
- appendImpl ||
40
- ((file, data) => {
41
- fs.mkdirSync(path.dirname(file), { recursive: true });
42
- fs.appendFileSync(file, data);
43
- });
44
- this.forwardImpl = forwardImpl;
45
- this.turn = null; // in-flight turn { user, assistant:[], tools:[], ts }
46
- this.lines = []; // un-flushed markdown lines
47
- this.timer = null;
48
- }
49
-
50
- ingest(ev) {
51
- if (!ev || typeof ev !== 'object') return;
52
- switch (ev.type) {
53
- case 'chat-user':
54
- this.turn = { user: ev.text || '', assistant: [], tools: [], ts: ev.ts };
55
- break;
56
- case 'chat-stream': {
57
- if (!this.turn) this.turn = { user: '', assistant: [], tools: [], ts: ev.ts };
58
- const c = ev.chunk || {};
59
- if (c.type === 'text' && typeof c.text === 'string') this.turn.assistant.push(c.text);
60
- else if (c.type === 'tool-use') this.turn.tools.push(c.name || c.tool || 'tool');
61
- break;
62
- }
63
- case 'chat-end':
64
- this._finalizeTurn();
65
- this._scheduleFlush();
66
- break;
67
- default:
68
- break;
69
- }
70
- }
71
-
72
- _finalizeTurn() {
73
- if (!this.turn) return;
74
- const t = this.turn;
75
- this.turn = null;
76
- const ts = new Date(t.ts || this.nowImpl()).toISOString();
77
- if (t.user) this.lines.push(`### You — ${ts}`, '', t.user, '');
78
- if (t.tools.length) this.lines.push(`> tools: ${[...new Set(t.tools)].join(', ')}`, '');
79
- const assistant = t.assistant.join('').trim();
80
- if (assistant) this.lines.push(`### ${this.agentName}`, '', assistant, '');
81
- }
82
-
83
- _scheduleFlush() {
84
- if (this.timer) return;
85
- this.timer = setTimeout(() => {
86
- this.timer = null;
87
- this.flush();
88
- }, this.flushMs);
89
- if (this.timer.unref) this.timer.unref();
90
- }
91
-
92
- /** Append the un-flushed markdown to today's file and forward it. Never throws. */
93
- flush() {
94
- if (!this.lines.length) return;
95
- const body = this.lines.join('\n') + '\n';
96
- this.lines = [];
97
- const date = new Date(this.nowImpl()).toISOString().slice(0, 10);
98
- const file = path.join(this.dir, `${date}.md`);
99
- try {
100
- this.appendImpl(file, body);
101
- } catch {
102
- /* read-only fs — local backup degrades, never breaks the chat */
103
- }
104
- if (this.forwardImpl) {
105
- try {
106
- this.forwardImpl({ file, date, markdown: body });
107
- } catch {
108
- /* forwarding is best-effort */
109
- }
110
- }
111
- }
112
-
113
- stop() {
114
- if (this.timer) {
115
- clearTimeout(this.timer);
116
- this.timer = null;
117
- }
118
- this._finalizeTurn();
119
- this.flush();
120
- }
121
- }
1
+ // TranscriptRecorder — the conversation *content* channel (separate from the
2
+ // events/health feed in session-reporter.mjs).
3
+ //
4
+ // WHY a separate channel: per the locked design, the feed carries WHAT happened
5
+ // (redacted, no words); the actual conversation is captured here and synced via
6
+ // bmo-sync — modeled on the team's transcript-backup (Claude `Stop` hook →
7
+ // jsonl→markdown → claude_backups/<user>/<date>/). bmo-sync REPLACES that Dropbox
8
+ // transport: each user's transcript is appended to an account-scoped store, so we
9
+ // avoid the "… conflicted copy …" litter a shared mutable file accrues (CLAUDE.md
10
+ // principle #1 — and the exact failure observed in the team's Dropbox folder).
11
+ //
12
+ // HOW: taps the ActivityBus (which carries the raw turn text the wild-workspace
13
+ // chat already streams), buffers turns to markdown, and APPENDS incrementally
14
+ // (append-only, never a rewrite — no shared mutable doc) to
15
+ // ~/.wild-workspace/transcripts/<workspaceId>/<date>.md — OUTSIDE the synced
16
+ // repo. The local write is the user's own data (harmless); forwarding to us is
17
+ // consent-gated (the caller supplies `forwardImpl` only when consent is on).
18
+ // Debounced ~10 min like hook_trigger.py.
19
+
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+
23
+ const FLUSH_MS = 10 * 60 * 1000;
24
+
25
+ export class TranscriptRecorder {
26
+ constructor({
27
+ dir,
28
+ agentName = 'Agent',
29
+ flushMs = FLUSH_MS,
30
+ nowImpl = () => Date.now(),
31
+ appendImpl,
32
+ forwardImpl = null,
33
+ } = {}) {
34
+ this.dir = dir;
35
+ this.agentName = agentName || 'Agent';
36
+ this.flushMs = flushMs;
37
+ this.nowImpl = nowImpl;
38
+ this.appendImpl =
39
+ appendImpl ||
40
+ ((file, data) => {
41
+ fs.mkdirSync(path.dirname(file), { recursive: true });
42
+ fs.appendFileSync(file, data);
43
+ });
44
+ this.forwardImpl = forwardImpl;
45
+ this.turn = null; // in-flight turn { user, assistant:[], tools:[], ts }
46
+ this.lines = []; // un-flushed markdown lines
47
+ this.timer = null;
48
+ }
49
+
50
+ ingest(ev) {
51
+ if (!ev || typeof ev !== 'object') return;
52
+ switch (ev.type) {
53
+ case 'chat-user':
54
+ this.turn = { user: ev.text || '', assistant: [], tools: [], ts: ev.ts };
55
+ break;
56
+ case 'chat-stream': {
57
+ if (!this.turn) this.turn = { user: '', assistant: [], tools: [], ts: ev.ts };
58
+ const c = ev.chunk || {};
59
+ if (c.type === 'text' && typeof c.text === 'string') this.turn.assistant.push(c.text);
60
+ else if (c.type === 'tool-use') this.turn.tools.push(c.name || c.tool || 'tool');
61
+ break;
62
+ }
63
+ case 'chat-end':
64
+ this._finalizeTurn();
65
+ this._scheduleFlush();
66
+ break;
67
+ default:
68
+ break;
69
+ }
70
+ }
71
+
72
+ _finalizeTurn() {
73
+ if (!this.turn) return;
74
+ const t = this.turn;
75
+ this.turn = null;
76
+ const ts = new Date(t.ts || this.nowImpl()).toISOString();
77
+ if (t.user) this.lines.push(`### You — ${ts}`, '', t.user, '');
78
+ if (t.tools.length) this.lines.push(`> tools: ${[...new Set(t.tools)].join(', ')}`, '');
79
+ const assistant = t.assistant.join('').trim();
80
+ if (assistant) this.lines.push(`### ${this.agentName}`, '', assistant, '');
81
+ }
82
+
83
+ _scheduleFlush() {
84
+ if (this.timer) return;
85
+ this.timer = setTimeout(() => {
86
+ this.timer = null;
87
+ this.flush();
88
+ }, this.flushMs);
89
+ if (this.timer.unref) this.timer.unref();
90
+ }
91
+
92
+ /** Append the un-flushed markdown to today's file and forward it. Never throws. */
93
+ flush() {
94
+ if (!this.lines.length) return;
95
+ const body = this.lines.join('\n') + '\n';
96
+ this.lines = [];
97
+ const date = new Date(this.nowImpl()).toISOString().slice(0, 10);
98
+ const file = path.join(this.dir, `${date}.md`);
99
+ try {
100
+ this.appendImpl(file, body);
101
+ } catch {
102
+ /* read-only fs — local backup degrades, never breaks the chat */
103
+ }
104
+ if (this.forwardImpl) {
105
+ try {
106
+ this.forwardImpl({ file, date, markdown: body });
107
+ } catch {
108
+ /* forwarding is best-effort */
109
+ }
110
+ }
111
+ }
112
+
113
+ stop() {
114
+ if (this.timer) {
115
+ clearTimeout(this.timer);
116
+ this.timer = null;
117
+ }
118
+ this._finalizeTurn();
119
+ this.flush();
120
+ }
121
+ }
@@ -1,46 +1,46 @@
1
- // Turn MCP config — what the agent's MCP toolbox is on a user chat turn.
2
- //
3
- // The turn exposes TWO in-repo MCP servers, isolated by --strict-mcp-config:
4
- // - bazaar — reach onto the marketplace shelf (§3.7)
5
- // - canvas — build the user a custom block on their canvas (§3.3)
6
- // Both run with the same node and share our state dir via WILD_WORKSPACE_GLOBAL_DIR,
7
- // so the spawned children are a single source of truth with the main server. We
8
- // write ONE merged config (a single --mcp-config the main server passes), rather
9
- // than relying on multiple --mcp-config flags, so the merge is explicit and tested.
10
-
11
- import fs from 'node:fs';
12
- import path from 'node:path';
13
-
14
- import { MCP_SERVER_PATH as BAZAAR_MCP_SERVER_PATH, BAZAAR_SYSTEM_PROMPT } from './bazaar/index.mjs';
15
- import { CANVAS_MCP_SERVER_PATH, CANVAS_SYSTEM_PROMPT } from './canvas/index.mjs';
16
-
17
- // The combined disposition the agent gets appended to its system prompt.
18
- export const TURN_SYSTEM_PROMPT = `${BAZAAR_SYSTEM_PROMPT}\n\n${CANVAS_SYSTEM_PROMPT}`;
19
-
20
- /**
21
- * Build the merged mcpServers map. Pure (no I/O) so it's unit-testable.
22
- * `globalDir` is pinned into each child's env so it finds the shared state dir.
23
- */
24
- export function turnMcpServers({ globalDir, nodePath = process.execPath } = {}) {
25
- const env = globalDir ? { WILD_WORKSPACE_GLOBAL_DIR: globalDir } : {};
26
- return {
27
- bazaar: { command: nodePath, args: [BAZAAR_MCP_SERVER_PATH], env },
28
- canvas: { command: nodePath, args: [CANVAS_MCP_SERVER_PATH], env },
29
- };
30
- }
31
-
32
- /**
33
- * Write the merged --mcp-config the turn loads. Returns the config path, or null
34
- * if it can't be written (read-only fs → the turn just runs without MCP tools).
35
- */
36
- export function writeTurnMcpConfig({ baseDir, globalDir, nodePath = process.execPath } = {}) {
37
- const cfg = { mcpServers: turnMcpServers({ globalDir, nodePath }) };
38
- const file = path.join(baseDir, 'turn-mcp-config.json');
39
- try {
40
- fs.mkdirSync(baseDir, { recursive: true });
41
- fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
42
- return file;
43
- } catch {
44
- return null;
45
- }
46
- }
1
+ // Turn MCP config — what the agent's MCP toolbox is on a user chat turn.
2
+ //
3
+ // The turn exposes TWO in-repo MCP servers, isolated by --strict-mcp-config:
4
+ // - bazaar — reach onto the marketplace shelf (§3.7)
5
+ // - canvas — build the user a custom block on their canvas (§3.3)
6
+ // Both run with the same node and share our state dir via WILD_WORKSPACE_GLOBAL_DIR,
7
+ // so the spawned children are a single source of truth with the main server. We
8
+ // write ONE merged config (a single --mcp-config the main server passes), rather
9
+ // than relying on multiple --mcp-config flags, so the merge is explicit and tested.
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ import { MCP_SERVER_PATH as BAZAAR_MCP_SERVER_PATH, BAZAAR_SYSTEM_PROMPT } from './bazaar/index.mjs';
15
+ import { CANVAS_MCP_SERVER_PATH, CANVAS_SYSTEM_PROMPT } from './canvas/index.mjs';
16
+
17
+ // The combined disposition the agent gets appended to its system prompt.
18
+ export const TURN_SYSTEM_PROMPT = `${BAZAAR_SYSTEM_PROMPT}\n\n${CANVAS_SYSTEM_PROMPT}`;
19
+
20
+ /**
21
+ * Build the merged mcpServers map. Pure (no I/O) so it's unit-testable.
22
+ * `globalDir` is pinned into each child's env so it finds the shared state dir.
23
+ */
24
+ export function turnMcpServers({ globalDir, nodePath = process.execPath } = {}) {
25
+ const env = globalDir ? { WILD_WORKSPACE_GLOBAL_DIR: globalDir } : {};
26
+ return {
27
+ bazaar: { command: nodePath, args: [BAZAAR_MCP_SERVER_PATH], env },
28
+ canvas: { command: nodePath, args: [CANVAS_MCP_SERVER_PATH], env },
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Write the merged --mcp-config the turn loads. Returns the config path, or null
34
+ * if it can't be written (read-only fs → the turn just runs without MCP tools).
35
+ */
36
+ export function writeTurnMcpConfig({ baseDir, globalDir, nodePath = process.execPath } = {}) {
37
+ const cfg = { mcpServers: turnMcpServers({ globalDir, nodePath }) };
38
+ const file = path.join(baseDir, 'turn-mcp-config.json');
39
+ try {
40
+ fs.mkdirSync(baseDir, { recursive: true });
41
+ fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
42
+ return file;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }