@venturewild/workspace 0.5.0 → 0.5.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.
- package/LICENSE +21 -21
- package/README.md +112 -112
- package/package.json +85 -85
- package/server/bin/wild-workspace.mjs +1096 -1096
- package/server/src/account.mjs +114 -114
- package/server/src/agent-login.mjs +146 -146
- package/server/src/agent-readiness.mjs +200 -200
- package/server/src/agent.mjs +468 -468
- package/server/src/bazaar/core.mjs +790 -730
- package/server/src/bazaar/index.mjs +88 -88
- package/server/src/bazaar/mcp-server.mjs +417 -417
- package/server/src/bazaar/mock-tickup.mjs +97 -97
- package/server/src/bazaar/preview-server.mjs +95 -95
- package/server/src/bazaar/seed-recipes/customer-feedback-form/know-how.md +23 -23
- package/server/src/bazaar/seed-recipes/customer-feedback-form/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/landing-page-launch/know-how.md +29 -29
- package/server/src/bazaar/seed-recipes/landing-page-launch/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/personal-portfolio/know-how.md +21 -21
- package/server/src/bazaar/seed-recipes/personal-portfolio/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/receipt-sorter/know-how.md +31 -31
- package/server/src/bazaar/seed-recipes/receipt-sorter/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/know-how.md +79 -79
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/recipe.json +40 -40
- package/server/src/canvas/core.mjs +446 -446
- package/server/src/canvas/index.mjs +42 -42
- package/server/src/canvas/mcp-server.mjs +253 -253
- package/server/src/canvas-rails.mjs +108 -108
- package/server/src/config.mjs +404 -404
- package/server/src/daemon-bin.mjs +110 -110
- package/server/src/daemon-supervisor.mjs +285 -285
- package/server/src/doctor.mjs +375 -375
- package/server/src/inbox.mjs +86 -86
- package/server/src/index.mjs +3279 -3181
- package/server/src/listings-rails.mjs +126 -0
- package/server/src/logpaths.mjs +98 -98
- package/server/src/observability.mjs +45 -45
- package/server/src/operator.mjs +92 -92
- package/server/src/pairing.mjs +137 -137
- package/server/src/service.mjs +515 -515
- package/server/src/session-reporter.mjs +201 -201
- package/server/src/settings.mjs +145 -145
- package/server/src/share.mjs +182 -182
- package/server/src/skills.mjs +213 -213
- package/server/src/supervisor.mjs +647 -647
- package/server/src/support-consent.mjs +133 -133
- package/server/src/sync.mjs +248 -248
- package/server/src/transcript.mjs +121 -121
- package/server/src/turn-mcp.mjs +46 -46
- package/server/src/usage.mjs +405 -405
- package/server/src/workspace-registry.mjs +295 -295
- package/server/src/workspaces.mjs +145 -135
- package/web/dist/assets/index-BXq-Irj8.js +131 -0
- package/web/dist/assets/index-CzUrGoMW.css +32 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DWNJ55qg.css +0 -32
- package/web/dist/assets/index-YlSTL4Wv.js +0 -131
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
// CanvasRails — per-identity canvas state on the rails (multi-host step 1).
|
|
2
|
-
//
|
|
3
|
-
// WHY: canvas layout/templates/user-theme used to be per-INSTALL (one folder on
|
|
4
|
-
// one machine). Multi-host (docs/multi-host-workspaces-design.md §8.1) makes them
|
|
5
|
-
// per-(workspace, person) on VW's rails, so a person's arrangement follows them
|
|
6
|
-
// across hosts/devices/reinstalls. The rails (bmo-sync) are the source of truth;
|
|
7
|
-
// the local ~/.wild-workspace/canvas/people/<personKey>/ files become a
|
|
8
|
-
// read-through + offline cache — the same "server is truth, cache below it"
|
|
9
|
-
// pattern lifted one level up.
|
|
10
|
-
//
|
|
11
|
-
// This is a thin client over bmo-sync's POST /api/canvas-state/{pull,push}
|
|
12
|
-
// (account-token self-authed, exactly like /api/telemetry). person_id and the
|
|
13
|
-
// workspace_slug are DERIVED server-side from the account token today — a caller
|
|
14
|
-
// can only touch its own account's row (no IDOR). Modeled on session-reporter.mjs.
|
|
15
|
-
//
|
|
16
|
-
// degrade-never-throw: every method fails soft (pull → {ok:false}, push → false)
|
|
17
|
-
// so the canvas keeps working from the local cache when the rails are down or the
|
|
18
|
-
// install has no account. `pull` distinguishes "rails answered, empty" (ok:true,
|
|
19
|
-
// state:null → migration may fire) from "rails unreachable" (ok:false → serve the
|
|
20
|
-
// local cache, do NOT migrate).
|
|
21
|
-
|
|
22
|
-
const DEFAULT_TIMEOUT_MS = 3000;
|
|
23
|
-
|
|
24
|
-
export class CanvasRails {
|
|
25
|
-
constructor({
|
|
26
|
-
bmoSyncUrl,
|
|
27
|
-
accountToken,
|
|
28
|
-
slug = null,
|
|
29
|
-
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
30
|
-
fetchImpl = (...a) => globalThis.fetch(...a),
|
|
31
|
-
} = {}) {
|
|
32
|
-
this.bmoSyncUrl = bmoSyncUrl ? bmoSyncUrl.replace(/\/$/, '') : null;
|
|
33
|
-
this.accountToken = accountToken || null;
|
|
34
|
-
this.slug = slug;
|
|
35
|
-
this.timeoutMs = timeoutMs;
|
|
36
|
-
this.fetchImpl = fetchImpl;
|
|
37
|
-
// Inert without a token (can't key it) or without a server URL. Unlike
|
|
38
|
-
// telemetry we do NOT exclude localhost — the e2e test points a local
|
|
39
|
-
// wild-workspace at a local bmo-sync, and dev installs have no account
|
|
40
|
-
// token anyway, so they stay inert without an extra guard.
|
|
41
|
-
this.capable = Boolean(this.accountToken) && Boolean(this.bmoSyncUrl);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async _post(path, body) {
|
|
45
|
-
if (!this.capable) return null;
|
|
46
|
-
const ctrl = new AbortController();
|
|
47
|
-
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
48
|
-
if (timer.unref) timer.unref();
|
|
49
|
-
try {
|
|
50
|
-
const r = await this.fetchImpl(`${this.bmoSyncUrl}${path}`, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: { 'content-type': 'application/json' },
|
|
53
|
-
body: JSON.stringify({ account_token: this.accountToken, ...body }),
|
|
54
|
-
signal: ctrl.signal,
|
|
55
|
-
});
|
|
56
|
-
if (!r || !r.ok) return null;
|
|
57
|
-
return await r.json().catch(() => null);
|
|
58
|
-
} catch {
|
|
59
|
-
return null; // network / abort / parse — caller degrades to the local cache
|
|
60
|
-
} finally {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Pull this person's canvas state from the rails.
|
|
67
|
-
* @returns {Promise<{ok: boolean, state: object|null}>}
|
|
68
|
-
* ok:true → the rails answered. state is the {layout,templates,userTheme}
|
|
69
|
-
* object, or null when the rails have nothing yet (→ migration may fire).
|
|
70
|
-
* ok:false → the rails were unreachable / not configured (→ serve the local cache).
|
|
71
|
-
*/
|
|
72
|
-
async pull(workspaceSlug = this.slug) {
|
|
73
|
-
const resp = await this._post('/api/canvas-state/pull', { workspace_slug: workspaceSlug });
|
|
74
|
-
if (!resp || resp.ok !== true) return { ok: false, state: null };
|
|
75
|
-
// resp.json is the stored blob as a STRING (or null/absent when empty).
|
|
76
|
-
if (resp.json == null || resp.json === '') return { ok: true, state: null };
|
|
77
|
-
try {
|
|
78
|
-
const state = JSON.parse(resp.json);
|
|
79
|
-
return { ok: true, state: state && typeof state === 'object' ? state : null };
|
|
80
|
-
} catch {
|
|
81
|
-
return { ok: true, state: null }; // corrupt row → treat as empty
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Push this person's FULL canvas snapshot to the rails (last-write-wins; Node
|
|
87
|
-
* owns the merge, so the rails always receive the complete {layout,templates,
|
|
88
|
-
* userTheme}). Best-effort.
|
|
89
|
-
* @returns {Promise<boolean>} true iff the rails accepted the write.
|
|
90
|
-
*/
|
|
91
|
-
async push(snapshot, workspaceSlug = this.slug) {
|
|
92
|
-
const resp = await this._post('/api/canvas-state/push', {
|
|
93
|
-
workspace_slug: workspaceSlug,
|
|
94
|
-
json: JSON.stringify(snapshot ?? {}),
|
|
95
|
-
});
|
|
96
|
-
return Boolean(resp && resp.ok === true);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** Build the rails client from server config + account (or null when not logged in). */
|
|
101
|
-
export function createCanvasRails(config, account, fetchImpl) {
|
|
102
|
-
return new CanvasRails({
|
|
103
|
-
bmoSyncUrl: config?.bmoSyncServerUrl,
|
|
104
|
-
accountToken: account?.accountToken,
|
|
105
|
-
slug: account?.slug || config?.account?.slug || null,
|
|
106
|
-
fetchImpl,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
1
|
+
// CanvasRails — per-identity canvas state on the rails (multi-host step 1).
|
|
2
|
+
//
|
|
3
|
+
// WHY: canvas layout/templates/user-theme used to be per-INSTALL (one folder on
|
|
4
|
+
// one machine). Multi-host (docs/multi-host-workspaces-design.md §8.1) makes them
|
|
5
|
+
// per-(workspace, person) on VW's rails, so a person's arrangement follows them
|
|
6
|
+
// across hosts/devices/reinstalls. The rails (bmo-sync) are the source of truth;
|
|
7
|
+
// the local ~/.wild-workspace/canvas/people/<personKey>/ files become a
|
|
8
|
+
// read-through + offline cache — the same "server is truth, cache below it"
|
|
9
|
+
// pattern lifted one level up.
|
|
10
|
+
//
|
|
11
|
+
// This is a thin client over bmo-sync's POST /api/canvas-state/{pull,push}
|
|
12
|
+
// (account-token self-authed, exactly like /api/telemetry). person_id and the
|
|
13
|
+
// workspace_slug are DERIVED server-side from the account token today — a caller
|
|
14
|
+
// can only touch its own account's row (no IDOR). Modeled on session-reporter.mjs.
|
|
15
|
+
//
|
|
16
|
+
// degrade-never-throw: every method fails soft (pull → {ok:false}, push → false)
|
|
17
|
+
// so the canvas keeps working from the local cache when the rails are down or the
|
|
18
|
+
// install has no account. `pull` distinguishes "rails answered, empty" (ok:true,
|
|
19
|
+
// state:null → migration may fire) from "rails unreachable" (ok:false → serve the
|
|
20
|
+
// local cache, do NOT migrate).
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
23
|
+
|
|
24
|
+
export class CanvasRails {
|
|
25
|
+
constructor({
|
|
26
|
+
bmoSyncUrl,
|
|
27
|
+
accountToken,
|
|
28
|
+
slug = null,
|
|
29
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
30
|
+
fetchImpl = (...a) => globalThis.fetch(...a),
|
|
31
|
+
} = {}) {
|
|
32
|
+
this.bmoSyncUrl = bmoSyncUrl ? bmoSyncUrl.replace(/\/$/, '') : null;
|
|
33
|
+
this.accountToken = accountToken || null;
|
|
34
|
+
this.slug = slug;
|
|
35
|
+
this.timeoutMs = timeoutMs;
|
|
36
|
+
this.fetchImpl = fetchImpl;
|
|
37
|
+
// Inert without a token (can't key it) or without a server URL. Unlike
|
|
38
|
+
// telemetry we do NOT exclude localhost — the e2e test points a local
|
|
39
|
+
// wild-workspace at a local bmo-sync, and dev installs have no account
|
|
40
|
+
// token anyway, so they stay inert without an extra guard.
|
|
41
|
+
this.capable = Boolean(this.accountToken) && Boolean(this.bmoSyncUrl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async _post(path, body) {
|
|
45
|
+
if (!this.capable) return null;
|
|
46
|
+
const ctrl = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
48
|
+
if (timer.unref) timer.unref();
|
|
49
|
+
try {
|
|
50
|
+
const r = await this.fetchImpl(`${this.bmoSyncUrl}${path}`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'content-type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ account_token: this.accountToken, ...body }),
|
|
54
|
+
signal: ctrl.signal,
|
|
55
|
+
});
|
|
56
|
+
if (!r || !r.ok) return null;
|
|
57
|
+
return await r.json().catch(() => null);
|
|
58
|
+
} catch {
|
|
59
|
+
return null; // network / abort / parse — caller degrades to the local cache
|
|
60
|
+
} finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pull this person's canvas state from the rails.
|
|
67
|
+
* @returns {Promise<{ok: boolean, state: object|null}>}
|
|
68
|
+
* ok:true → the rails answered. state is the {layout,templates,userTheme}
|
|
69
|
+
* object, or null when the rails have nothing yet (→ migration may fire).
|
|
70
|
+
* ok:false → the rails were unreachable / not configured (→ serve the local cache).
|
|
71
|
+
*/
|
|
72
|
+
async pull(workspaceSlug = this.slug) {
|
|
73
|
+
const resp = await this._post('/api/canvas-state/pull', { workspace_slug: workspaceSlug });
|
|
74
|
+
if (!resp || resp.ok !== true) return { ok: false, state: null };
|
|
75
|
+
// resp.json is the stored blob as a STRING (or null/absent when empty).
|
|
76
|
+
if (resp.json == null || resp.json === '') return { ok: true, state: null };
|
|
77
|
+
try {
|
|
78
|
+
const state = JSON.parse(resp.json);
|
|
79
|
+
return { ok: true, state: state && typeof state === 'object' ? state : null };
|
|
80
|
+
} catch {
|
|
81
|
+
return { ok: true, state: null }; // corrupt row → treat as empty
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Push this person's FULL canvas snapshot to the rails (last-write-wins; Node
|
|
87
|
+
* owns the merge, so the rails always receive the complete {layout,templates,
|
|
88
|
+
* userTheme}). Best-effort.
|
|
89
|
+
* @returns {Promise<boolean>} true iff the rails accepted the write.
|
|
90
|
+
*/
|
|
91
|
+
async push(snapshot, workspaceSlug = this.slug) {
|
|
92
|
+
const resp = await this._post('/api/canvas-state/push', {
|
|
93
|
+
workspace_slug: workspaceSlug,
|
|
94
|
+
json: JSON.stringify(snapshot ?? {}),
|
|
95
|
+
});
|
|
96
|
+
return Boolean(resp && resp.ok === true);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Build the rails client from server config + account (or null when not logged in). */
|
|
101
|
+
export function createCanvasRails(config, account, fetchImpl) {
|
|
102
|
+
return new CanvasRails({
|
|
103
|
+
bmoSyncUrl: config?.bmoSyncServerUrl,
|
|
104
|
+
accountToken: account?.accountToken,
|
|
105
|
+
slug: account?.slug || config?.account?.slug || null,
|
|
106
|
+
fetchImpl,
|
|
107
|
+
});
|
|
108
|
+
}
|