@hellolynk-ai/workbuddy-mcp 0.0.0-stage → 0.3.96
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 +92 -0
- package/README.md +88 -2
- package/bin/hellolynk-client-setup.mjs +2748 -0
- package/bin/hellolynk-mcp-config-shape.mjs +23 -0
- package/clients/core/hellolynk-client-account.mjs +180 -0
- package/clients/core/hellolynk-client-core.mjs +770 -0
- package/clients/core/hellolynk-client-profile.mjs +191 -0
- package/clients/core/hellolynk-client-update-entry.mjs +31 -0
- package/clients/core/hellolynk-client-update-runtime.mjs +600 -0
- package/clients/core/hellolynk-client-update.mjs +453 -0
- package/clients/core/hellolynk-contact-request-e2ee.mjs +867 -0
- package/clients/core/hellolynk-device-enrollment.mjs +435 -0
- package/clients/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
- package/clients/core/hellolynk-direct-text-e2ee.mjs +1207 -0
- package/clients/core/hellolynk-host-auth.mjs +347 -0
- package/clients/core/hellolynk-host-protected-store.mjs +2721 -0
- package/clients/core/hellolynk-host-transport-client.mjs +946 -0
- package/clients/core/hellolynk-host.mjs +2374 -0
- package/clients/core/hellolynk-human-confirmation-ledger.mjs +256 -0
- package/clients/core/hellolynk-human-relationship-controller.mjs +1234 -0
- package/clients/core/hellolynk-inbox-read-state.mjs +11 -0
- package/clients/core/hellolynk-indexed-message-store.mjs +1558 -0
- package/clients/core/hellolynk-instance-conversation.mjs +185 -0
- package/clients/core/hellolynk-local-contact-requests.mjs +1733 -0
- package/clients/core/hellolynk-local-contacts.mjs +1228 -0
- package/clients/core/hellolynk-local-data-migration.mjs +766 -0
- package/clients/core/hellolynk-local-data-protection.mjs +747 -0
- package/clients/core/hellolynk-local-identity-trust.mjs +274 -0
- package/clients/core/hellolynk-local-messaging.mjs +3043 -0
- package/clients/core/hellolynk-local-peer-transport.mjs +103 -0
- package/clients/core/hellolynk-local-process-lock.mjs +157 -0
- package/clients/core/hellolynk-model-inbox.mjs +230 -0
- package/clients/core/hellolynk-model-notices.cjs +60 -0
- package/clients/core/hellolynk-native-addressing-tools.cjs +231 -0
- package/clients/core/hellolynk-native-conversations.mjs +280 -0
- package/clients/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
- package/clients/core/hellolynk-native-inbox-runtime.mjs +553 -0
- package/clients/core/hellolynk-native-notice.mjs +50 -0
- package/clients/core/hellolynk-native-reply-routes.mjs +96 -0
- package/clients/core/hellolynk-native-task-index.cjs +305 -0
- package/clients/core/hellolynk-native-worker-lease.mjs +84 -0
- package/clients/core/hellolynk-neutral-host-assembly.mjs +955 -0
- package/clients/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/clients/core/hellolynk-neutral-host-launcher.mjs +665 -0
- package/clients/core/hellolynk-neutral-host-process.mjs +4374 -0
- package/clients/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
- package/clients/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
- package/clients/core/hellolynk-platform-credential-store.mjs +773 -0
- package/clients/core/hellolynk-relay-client-cli.mjs +232 -0
- package/clients/core/hellolynk-release-retention.mjs +160 -0
- package/clients/core/hellolynk-runtime-safety.cjs +471 -0
- package/clients/core/hellolynk-workbench-action-grants.mjs +457 -0
- package/clients/core/hellolynk-workbench-connection.mjs +234 -0
- package/clients/platform/windows/credential-store.ps1 +844 -0
- package/clients/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/clients/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
- package/clients/platform/windows/hellolynk-local-data-key.ps1 +300 -0
- package/clients/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
- package/clients/workbench/contract.mjs +1314 -0
- package/clients/workbench/service.mjs +485 -0
- package/hellolynk-client-pack-manifest.json +884 -0
- package/hellolynk-client-pack-pin.json +4 -0
- package/integrations/workbuddy/package.json +15 -0
- package/integrations/workbuddy/server/host-lifecycle.mjs +249 -0
- package/integrations/workbuddy/server/mcp-proxy.mjs +258 -0
- package/integrations/workbuddy/server/mcp-server.mjs +357 -0
- package/integrations/workbuddy/server/model-call-origin.mjs +87 -0
- package/integrations/workbuddy/server/native-task-cdp.mjs +175 -0
- package/integrations/workbuddy/server/native-task-debug-startup.mjs +117 -0
- package/integrations/workbuddy/server/native-task-debug-startup.ps1 +40 -0
- package/integrations/workbuddy/server/native-task-mcp-registration.mjs +30 -0
- package/integrations/workbuddy/server/native-task-service-registration.mjs +115 -0
- package/integrations/workbuddy/server/native-task-worker.mjs +127 -0
- package/integrations/workbuddy/server/neutral-host-adapter.mjs +639 -0
- package/integrations/workbuddy/server/origin-hook.mjs +111 -0
- package/integrations/workbuddy/server/origin-routes.mjs +211 -0
- package/integrations/workbuddy/server/package-bootstrap.mjs +111 -0
- package/integrations/workbuddy/server/workbench-candidate.mjs +202 -0
- package/integrations/workbuddy/server/workbench-hidden-launcher.vbs +46 -0
- package/integrations/workbuddy/server/workbench-process.mjs +373 -0
- package/integrations/workbuddy/server/workbench-task.ps1 +196 -0
- package/integrations/workbuddy/skills/hellolynk-inbox/SKILL.md +9 -0
- package/integrations/workbuddy/skills/hellolynk-inbox/workbuddy.json +6 -0
- package/package.json +65 -4
- package/runtime/connect-activate.mjs +837 -0
- package/runtime/core/hellolynk-agent-configurator.mjs +701 -0
- package/runtime/core/hellolynk-client-account.mjs +180 -0
- package/runtime/core/hellolynk-client-core.mjs +770 -0
- package/runtime/core/hellolynk-client-profile.mjs +191 -0
- package/runtime/core/hellolynk-client-update-entry.mjs +31 -0
- package/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
- package/runtime/core/hellolynk-client-update.mjs +453 -0
- package/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
- package/runtime/core/hellolynk-device-enrollment.mjs +435 -0
- package/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
- package/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
- package/runtime/core/hellolynk-host-auth.mjs +347 -0
- package/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
- package/runtime/core/hellolynk-host-transport-client.mjs +946 -0
- package/runtime/core/hellolynk-host.mjs +2374 -0
- package/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
- package/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
- package/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
- package/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
- package/runtime/core/hellolynk-instance-conversation.mjs +185 -0
- package/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
- package/runtime/core/hellolynk-local-contacts.mjs +1228 -0
- package/runtime/core/hellolynk-local-data-migration.mjs +766 -0
- package/runtime/core/hellolynk-local-data-protection.mjs +747 -0
- package/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
- package/runtime/core/hellolynk-local-messaging.mjs +3043 -0
- package/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
- package/runtime/core/hellolynk-local-process-lock.mjs +157 -0
- package/runtime/core/hellolynk-model-inbox.mjs +230 -0
- package/runtime/core/hellolynk-model-notices.cjs +60 -0
- package/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
- package/runtime/core/hellolynk-native-conversations.mjs +280 -0
- package/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
- package/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
- package/runtime/core/hellolynk-native-notice.mjs +50 -0
- package/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
- package/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
- package/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
- package/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
- package/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
- package/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
- package/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
- package/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
- package/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
- package/runtime/core/hellolynk-release-retention.mjs +160 -0
- package/runtime/core/hellolynk-runtime-safety.cjs +471 -0
- package/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
- package/runtime/core/hellolynk-workbench-connection.mjs +234 -0
- package/runtime/neutral-host-bootstrap.mjs +57 -0
- package/runtime/neutral-host-release-entry.mjs +81 -0
- package/runtime/neutral-host-release-loader.mjs +156 -0
- package/runtime/neutral-host-release-manifest.json +302 -0
- package/runtime/neutral-host-release-pin.json +4 -0
- package/runtime/platform/windows/configure-agent.ps1 +281 -0
- package/runtime/platform/windows/credential-store.ps1 +844 -0
- package/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
- package/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
- package/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
- package/runtime/workbench/candidate-bootstrap.mjs +54 -0
- package/runtime/workbench/contract.mjs +1314 -0
- package/runtime/workbench/hellolynk-workbench-build.json +215 -0
- package/runtime/workbench/host-capabilities.mjs +80 -0
- package/runtime/workbench/i18n.mjs +1127 -0
- package/runtime/workbench/loopback-surface.mjs +740 -0
- package/runtime/workbench/mcp-app-bundle.mjs +97 -0
- package/runtime/workbench/service.mjs +485 -0
- package/runtime/workbench/state.mjs +421 -0
- package/runtime/workbench/transports.mjs +171 -0
- package/runtime/workbench/ui/app.mjs +6120 -0
- package/runtime/workbench/ui/connection-form.mjs +155 -0
- package/runtime/workbench/ui/hellolynk-mark.png +0 -0
- package/runtime/workbench/ui/http-transport.mjs +247 -0
- package/runtime/workbench/ui/icons/arrow-left.svg +3 -0
- package/runtime/workbench/ui/icons/close.svg +3 -0
- package/runtime/workbench/ui/icons/contacts.svg +3 -0
- package/runtime/workbench/ui/icons/inbox.svg +3 -0
- package/runtime/workbench/ui/icons/refresh.svg +3 -0
- package/runtime/workbench/ui/icons/search.svg +3 -0
- package/runtime/workbench/ui/icons/security.svg +3 -0
- package/runtime/workbench/ui/icons/settings.svg +3 -0
- package/runtime/workbench/ui/index.html +139 -0
- package/runtime/workbench/ui/loopback-entry.mjs +26 -0
- package/runtime/workbench/ui/mcp-app-client.mjs +151 -0
- package/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
- package/runtime/workbench/ui/styles.css +686 -0
- package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
- package/tools/hellolynk-install-client-skills.mjs +1211 -0
- package/workbuddy-package-manifest.json +1 -0
- package/workbuddy-package-pin.json +1 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { withHelloLynkLocalMutationLock } from "../../../clients/core/hellolynk-local-messaging.mjs";
|
|
8
|
+
|
|
9
|
+
const exec = promisify(execFile);
|
|
10
|
+
const failure = code => Object.assign(new Error(code), { code });
|
|
11
|
+
|
|
12
|
+
// Transfer only our existing restore receipt. Never recreate or change the
|
|
13
|
+
// user's environment variable during a normal package update.
|
|
14
|
+
export async function transferWorkBuddyDebugStartupOwner({ storeRoot, previousInstallationId, installationId }) {
|
|
15
|
+
if (!path.isAbsolute(storeRoot || "") || ![previousInstallationId, installationId].every(value => /^[a-f0-9]{64}$/.test(value || ""))) throw failure("workbuddy_native_debug_startup_invalid");
|
|
16
|
+
await assertSafeParents(storeRoot);
|
|
17
|
+
const filename = path.join(storeRoot, "workbuddy-native-debug-startup.json");
|
|
18
|
+
return withHelloLynkLocalMutationLock(filename, async () => {
|
|
19
|
+
const state = await readState(filename);
|
|
20
|
+
if (!state) return;
|
|
21
|
+
if (state.phase !== "applied") throw failure("workbuddy_native_debug_startup_outcome_uncertain");
|
|
22
|
+
if (state.owner === installationId) return;
|
|
23
|
+
if (state.owner !== previousInstallationId) throw failure("workbuddy_native_debug_startup_conflict");
|
|
24
|
+
await writeState(filename, { ...state, owner: installationId });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The vendor reads this user environment variable on normal cold startup.
|
|
29
|
+
// No shortcut, vendor file, current window or login trigger is changed.
|
|
30
|
+
export async function configureWorkBuddyDebugStartup({ storeRoot, debugEndpoint, installationId, enableDebugStartup = false, environment = windowsEnvironment } = {}, operation = "Install") {
|
|
31
|
+
if (!["Install", "Uninstall"].includes(operation)) return { status: "unchanged" };
|
|
32
|
+
if (operation === "Install" && enableDebugStartup !== true) return { status: "not_selected" };
|
|
33
|
+
const endpoint = new URL(debugEndpoint || "http://127.0.0.1:9337");
|
|
34
|
+
if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.pathname !== "/" || endpoint.search || endpoint.hash || endpoint.username || endpoint.password || !path.isAbsolute(storeRoot || "") || !/^[a-f0-9]{64}$/.test(installationId || "")) throw failure("workbuddy_native_debug_startup_invalid");
|
|
35
|
+
const filename = path.join(storeRoot, "workbuddy-native-debug-startup.json");
|
|
36
|
+
await assertSafeParents(storeRoot);
|
|
37
|
+
return withHelloLynkLocalMutationLock(filename, async () => {
|
|
38
|
+
const state = await readState(filename);
|
|
39
|
+
if (operation === "Uninstall" && (!state || state.owner !== installationId)) return { status: "not_owned" };
|
|
40
|
+
const current = await environment.read();
|
|
41
|
+
if (operation === "Install") {
|
|
42
|
+
// First installation reuses a user-selected local port. Explicit callers
|
|
43
|
+
// still require their requested endpoint and never silently switch it.
|
|
44
|
+
if (!debugEndpoint && current !== null) {
|
|
45
|
+
if (!/^[1-9]\d{0,4}$/.test(current) || Number(current) > 65535) throw failure("workbuddy_native_debug_startup_conflict");
|
|
46
|
+
endpoint.port = current;
|
|
47
|
+
if (!endpoint.port) throw failure("workbuddy_native_debug_startup_conflict");
|
|
48
|
+
}
|
|
49
|
+
if (state && state.applied !== endpoint.port || current !== null && current !== endpoint.port) throw failure("workbuddy_native_debug_startup_conflict");
|
|
50
|
+
if (current === endpoint.port) {
|
|
51
|
+
if (state?.phase === "preparing") throw failure("workbuddy_native_debug_startup_outcome_uncertain");
|
|
52
|
+
// The replacement release takes over the existing original-value record.
|
|
53
|
+
if (state && state.owner !== installationId) await writeState(filename, { ...state, owner: installationId });
|
|
54
|
+
return { status: state ? "enabled" : "already_configured", debugEndpoint: endpoint.origin, coldStartRequired: true };
|
|
55
|
+
}
|
|
56
|
+
const next = { version: 1, variable: "WORKBUDDY_REMOTE_DEBUGGING_PORT", previous: null, applied: endpoint.port, owner: installationId, phase: "preparing" };
|
|
57
|
+
await writeState(filename, next);
|
|
58
|
+
const result = await environment.compareExchange(null, endpoint.port);
|
|
59
|
+
if (!result.exchanged) {
|
|
60
|
+
await fs.rm(filename);
|
|
61
|
+
throw failure("workbuddy_native_debug_startup_conflict");
|
|
62
|
+
}
|
|
63
|
+
await writeState(filename, { ...next, phase: "applied" });
|
|
64
|
+
return { status: "enabled", debugEndpoint: endpoint.origin, coldStartRequired: true, broadcastDelivered: result.broadcastDelivered };
|
|
65
|
+
}
|
|
66
|
+
if (state.phase !== "applied") return { status: "unconfirmed_not_restored" };
|
|
67
|
+
if (current !== state.applied) {
|
|
68
|
+
await fs.rm(filename);
|
|
69
|
+
return { status: "preserved_user_change" };
|
|
70
|
+
}
|
|
71
|
+
const result = await environment.compareExchange(state.applied, state.previous);
|
|
72
|
+
await fs.rm(filename);
|
|
73
|
+
return { status: result.exchanged ? "restored" : "preserved_user_change", broadcastDelivered: result.broadcastDelivered };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const windowsEnvironment = {
|
|
78
|
+
async read() { return (await callWindows("Read")).value; },
|
|
79
|
+
async compareExchange(expected, value) { return callWindows("CompareExchange", expected, value); },
|
|
80
|
+
};
|
|
81
|
+
async function callWindows(operation, expected, value) {
|
|
82
|
+
if (process.platform !== "win32") throw failure("workbuddy_native_debug_startup_windows_required");
|
|
83
|
+
try {
|
|
84
|
+
const args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File",
|
|
85
|
+
fileURLToPath(new URL("./native-task-debug-startup.ps1", import.meta.url)), "-Operation", operation];
|
|
86
|
+
if (operation === "CompareExchange") args.push("-Expected", expected ?? "absent", "-Value", value ?? "absent");
|
|
87
|
+
const { stdout } = await exec("powershell.exe", args, { windowsHide: true, timeout: 15_000, maxBuffer: 4096 });
|
|
88
|
+
const result = JSON.parse(stdout);
|
|
89
|
+
if (result?.ok !== true || operation === "Read" && result.value !== null && typeof result.value !== "string" || operation === "CompareExchange" && typeof result.exchanged !== "boolean") throw failure("workbuddy_native_debug_startup_failed");
|
|
90
|
+
return result;
|
|
91
|
+
} catch { throw failure("workbuddy_native_debug_startup_failed"); }
|
|
92
|
+
}
|
|
93
|
+
async function assertSafeParents(target) {
|
|
94
|
+
const root = path.parse(path.resolve(target)).root;
|
|
95
|
+
let current = root;
|
|
96
|
+
for (const part of path.relative(root, path.resolve(target)).split(path.sep).filter(Boolean)) {
|
|
97
|
+
current = path.join(current, part);
|
|
98
|
+
const stat = await fs.lstat(current);
|
|
99
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw failure("workbuddy_native_debug_startup_state_invalid");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function readState(filename) {
|
|
103
|
+
try {
|
|
104
|
+
const stat = await fs.lstat(filename);
|
|
105
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 2048) throw failure("workbuddy_native_debug_startup_state_invalid");
|
|
106
|
+
const state = JSON.parse(await fs.readFile(filename, "utf8"));
|
|
107
|
+
if (state?.version !== 1 || state.variable !== "WORKBUDDY_REMOTE_DEBUGGING_PORT" || state.previous !== null || !/^[a-f0-9]{64}$/.test(state.owner || "") || !["preparing", "applied"].includes(state.phase) || !/^\d{1,5}$/.test(state.applied) || Number(state.applied) < 1 || Number(state.applied) > 65535) throw failure("workbuddy_native_debug_startup_state_invalid");
|
|
108
|
+
return state;
|
|
109
|
+
} catch (error) { if (error.code === "ENOENT") return null; throw error; }
|
|
110
|
+
}
|
|
111
|
+
async function writeState(filename, state) {
|
|
112
|
+
const temporary = `${filename}.${crypto.randomUUID()}.tmp`;
|
|
113
|
+
try {
|
|
114
|
+
await fs.writeFile(temporary, JSON.stringify(state), { flag: "wx", mode: 0o600 });
|
|
115
|
+
await fs.rename(temporary, filename);
|
|
116
|
+
} finally { await fs.rm(temporary, { force: true }); }
|
|
117
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory=$true)][ValidateSet('Read','CompareExchange')][string]$Operation,
|
|
3
|
+
[ValidatePattern('^(absent|[0-9]{1,5})$')][string]$Expected = 'absent',
|
|
4
|
+
[ValidatePattern('^(absent|[0-9]{1,5})$')][string]$Value = 'absent'
|
|
5
|
+
)
|
|
6
|
+
$ErrorActionPreference = 'Stop'
|
|
7
|
+
try {
|
|
8
|
+
$variable = 'WORKBUDDY_REMOTE_DEBUGGING_PORT'
|
|
9
|
+
$current = [Environment]::GetEnvironmentVariable($variable, 'User')
|
|
10
|
+
if ($Operation -eq 'Read') {
|
|
11
|
+
[pscustomobject]@{ok=$true;value=$current} | ConvertTo-Json -Compress
|
|
12
|
+
exit 0
|
|
13
|
+
}
|
|
14
|
+
$expectedValue = $null
|
|
15
|
+
if ($Expected -ne 'absent') { $expectedValue = $Expected }
|
|
16
|
+
if ($current -cne $expectedValue) {
|
|
17
|
+
[pscustomobject]@{ok=$true;exchanged=$false} | ConvertTo-Json -Compress
|
|
18
|
+
exit 0
|
|
19
|
+
}
|
|
20
|
+
$nextValue = $null
|
|
21
|
+
if ($Value -ne 'absent') {
|
|
22
|
+
if ([int]$Value -lt 1 -or [int]$Value -gt 65535) { throw 'invalid_port' }
|
|
23
|
+
$nextValue = $Value
|
|
24
|
+
}
|
|
25
|
+
[Environment]::SetEnvironmentVariable($variable, $nextValue, 'User')
|
|
26
|
+
Add-Type -TypeDefinition @'
|
|
27
|
+
using System;
|
|
28
|
+
using System.Runtime.InteropServices;
|
|
29
|
+
public static class HelloLynkWorkBuddyEnvironmentBroadcast {
|
|
30
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
31
|
+
public static extern IntPtr SendMessageTimeout(IntPtr window, uint message, IntPtr wParam, string lParam, uint flags, uint timeout, out UIntPtr result);
|
|
32
|
+
}
|
|
33
|
+
'@
|
|
34
|
+
$result = [UIntPtr]::Zero
|
|
35
|
+
$delivered = [HelloLynkWorkBuddyEnvironmentBroadcast]::SendMessageTimeout([IntPtr]0xffff, 0x001a, [IntPtr]::Zero, 'Environment', 2, 3000, [ref]$result) -ne [IntPtr]::Zero
|
|
36
|
+
[pscustomobject]@{ok=$true;exchanged=$true;broadcastDelivered=$delivered} | ConvertTo-Json -Compress
|
|
37
|
+
} catch {
|
|
38
|
+
[Console]::Error.WriteLine('workbuddy_native_debug_startup_failed')
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { loadPinnedWorkBuddyProxy } from "./package-bootstrap.mjs";
|
|
4
|
+
import { createWorkBuddyNativeTaskClient } from "./native-task-cdp.mjs";
|
|
5
|
+
|
|
6
|
+
// Call during explicit HelloLynk installation/repair, after writing mcp.json.
|
|
7
|
+
// Normal WorkBuddy UI approval is configuration-hash-specific and therefore
|
|
8
|
+
// must be repeated for an installed package update that changes its command.
|
|
9
|
+
export async function registerWorkBuddyNativeMcp({ debugEndpoint, command, releaseRoot, expectedManifestSha256, storeRoot } = {}) {
|
|
10
|
+
const expected = workBuddyMcpExpectedConfig({ command, releaseRoot, expectedManifestSha256, storeRoot });
|
|
11
|
+
const stat = await fs.lstat(expected.command);
|
|
12
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw failure("workbuddy_native_mcp_node_invalid");
|
|
13
|
+
await loadPinnedWorkBuddyProxy({ root: expected.args[3], expectedManifestSha256 });
|
|
14
|
+
const result = await createWorkBuddyNativeTaskClient({ debugEndpoint }).approveHelloLynkMcp(expected);
|
|
15
|
+
// Approved, but WorkBuddy may still be starting the server; the native
|
|
16
|
+
// service connects it on its next check.
|
|
17
|
+
if (result.trusted && result.enabled && result.status !== "ready") return result;
|
|
18
|
+
const toolNames = new Set((result.tools || []).filter(tool=>tool.enabled).map(tool=>tool.name));
|
|
19
|
+
if (!result.trusted || !result.enabled || result.status !== "ready" || !["hellolynk_claim_message", "hellolynk_send_authorized_message", "hellolynk_reply_authorized_message"].every(name=>toolNames.has(name))) throw failure("workbuddy_native_mcp_registration_failed");
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The exact mcp.json entry an installer writes for this release. Paths are
|
|
24
|
+
// canonical, so a differently spelled path does not fail the exact match.
|
|
25
|
+
export function workBuddyMcpExpectedConfig({ command, releaseRoot, expectedManifestSha256, storeRoot } = {}) {
|
|
26
|
+
if (![command, releaseRoot, storeRoot].every(v=>typeof v==='string'&&path.isAbsolute(v)) || !/^node(?:\.exe)?$/i.test(path.basename(command)) || !/^[a-f0-9]{64}$/.test(expectedManifestSha256 || "")) throw failure("workbuddy_native_mcp_installation_invalid");
|
|
27
|
+
const root = path.resolve(releaseRoot);
|
|
28
|
+
return { command: path.resolve(command), args: [path.join(root, "integrations", "workbuddy", "server", "package-bootstrap.mjs"), "run", "--root", root, "--expected-manifest-sha256", expectedManifestSha256], storeRoot: path.resolve(storeRoot) };
|
|
29
|
+
}
|
|
30
|
+
function failure(code) { return Object.assign(new Error(code), { code }); }
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { withHelloLynkLocalMutationLock } from "../../../clients/core/hellolynk-local-messaging.mjs";
|
|
5
|
+
import { defaultNeutralHostStoreRoot } from "../../../clients/core/hellolynk-neutral-host-installation-store.mjs";
|
|
6
|
+
import { loadPinnedWorkBuddyProxy } from "./package-bootstrap.mjs";
|
|
7
|
+
import { workBuddyMcpExpectedConfig } from "./native-task-mcp-registration.mjs";
|
|
8
|
+
|
|
9
|
+
const failure = code => Object.assign(new Error(code), { code });
|
|
10
|
+
const sameRelease = (left, right) => left.root.toLowerCase() === path.resolve(right.root).toLowerCase() && left.expectedManifestSha256 === right.expectedManifestSha256;
|
|
11
|
+
|
|
12
|
+
// This record only identifies an explicitly installed, on-demand native task.
|
|
13
|
+
// The same short lock covers record changes and Verify/Start, so Uninstall can
|
|
14
|
+
// withdraw the record before stopping the task without a late MCP restarting it.
|
|
15
|
+
export async function withWorkBuddyNativeServiceRecord(storeRoot, operation) {
|
|
16
|
+
const configured = storeRoot || defaultNeutralHostStoreRoot();
|
|
17
|
+
if (!path.isAbsolute(configured)) throw failure("workbuddy_native_service_record_invalid");
|
|
18
|
+
const directory = path.resolve(configured);
|
|
19
|
+
const filename = path.join(directory, "workbuddy-native-service.json");
|
|
20
|
+
try { await safeParents(directory); }
|
|
21
|
+
catch (error) { if (error.code === "ENOENT") return operation(null); throw error; }
|
|
22
|
+
const write = async record => {
|
|
23
|
+
const temporary = `${filename}.${crypto.randomUUID()}.tmp`;
|
|
24
|
+
try {
|
|
25
|
+
await fs.writeFile(temporary, JSON.stringify(record), { flag: "wx", mode: 0o600 });
|
|
26
|
+
await fs.rename(temporary, filename);
|
|
27
|
+
} finally { await fs.rm(temporary, { force: true }); }
|
|
28
|
+
};
|
|
29
|
+
return withHelloLynkLocalMutationLock(filename, async () => operation({
|
|
30
|
+
async read() {
|
|
31
|
+
try {
|
|
32
|
+
const stat = await fs.lstat(filename);
|
|
33
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 8192) throw failure("workbuddy_native_service_record_invalid");
|
|
34
|
+
const record = JSON.parse(await fs.readFile(filename, "utf8"));
|
|
35
|
+
validate(record);
|
|
36
|
+
return record;
|
|
37
|
+
} catch (error) { if (error.code === "ENOENT") return null; throw error; }
|
|
38
|
+
},
|
|
39
|
+
async save(options, taskName) {
|
|
40
|
+
const record = { version: 1, root: path.resolve(options.root), expectedManifestSha256: options.expectedManifestSha256,
|
|
41
|
+
nodePath: options.nodePath || process.execPath, debugEndpoint: new URL(options.debugEndpoint).origin, taskName,
|
|
42
|
+
...(options.pendingMcpTrust ? { pendingMcpTrust: { command: options.pendingMcpTrust.command } } : {}) };
|
|
43
|
+
validate(record);
|
|
44
|
+
await safeParents(record.root);
|
|
45
|
+
await loadPinnedWorkBuddyProxy(record);
|
|
46
|
+
await write(record);
|
|
47
|
+
},
|
|
48
|
+
// Same release and task; only the trust request is dropped.
|
|
49
|
+
async clearMcpTrustRequest(record) {
|
|
50
|
+
const { pendingMcpTrust, ...kept } = record;
|
|
51
|
+
validate(kept);
|
|
52
|
+
await write(kept);
|
|
53
|
+
},
|
|
54
|
+
async removeOwned(options) {
|
|
55
|
+
const record = await this.read();
|
|
56
|
+
if (record && sameRelease(record, options)) await fs.rm(filename);
|
|
57
|
+
},
|
|
58
|
+
storeRoot: directory,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function startRecordedWorkBuddyNativeService({ storeRoot, loadProxy = loadPinnedWorkBuddyProxy } = {}) {
|
|
63
|
+
return withWorkBuddyNativeServiceRecord(storeRoot, async registry => {
|
|
64
|
+
const record = await registry?.read();
|
|
65
|
+
if (!record) return { configured: false, status: "not_installed" };
|
|
66
|
+
await safeParents(record.root);
|
|
67
|
+
const node = await fs.lstat(record.nodePath);
|
|
68
|
+
if (!node.isFile() || node.isSymbolicLink()) throw failure("workbuddy_native_service_record_invalid");
|
|
69
|
+
const proxy = await loadProxy(record);
|
|
70
|
+
if (typeof proxy.configureWorkBuddyNativeTaskService !== "function") throw failure("workbuddy_native_service_entry_unavailable");
|
|
71
|
+
const options = { ...record, storeRoot: registry.storeRoot };
|
|
72
|
+
const verified = await proxy.configureWorkBuddyNativeTaskService(options, "Verify");
|
|
73
|
+
if (verified?.ok !== true || verified.taskName !== record.taskName) throw failure("workbuddy_native_service_record_mismatch");
|
|
74
|
+
const started = await proxy.configureWorkBuddyNativeTaskService(options, "Start");
|
|
75
|
+
if (started?.ok !== true || started.taskName !== record.taskName) throw failure("workbuddy_native_service_record_mismatch");
|
|
76
|
+
// This is a Scheduler handoff, never a child process owned by the MCP Job.
|
|
77
|
+
return { configured: true, status: "service_started" };
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A package update changes the MCP command, so WorkBuddy asks for trust
|
|
82
|
+
// again. The update leaves one request in the record; the service of that
|
|
83
|
+
// same release approves exactly that configuration once, then clears it.
|
|
84
|
+
// Approval runs outside the lock: connecting starts an MCP that takes it.
|
|
85
|
+
export function workBuddyMcpTrustRequests(release) {
|
|
86
|
+
const pending = (record) => record?.pendingMcpTrust && sameRelease(record, release) ? record : null;
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
take: () => withWorkBuddyNativeServiceRecord(release.storeRoot, async registry => {
|
|
89
|
+
const record = pending(await registry?.read());
|
|
90
|
+
return record && workBuddyMcpExpectedConfig({ command: record.pendingMcpTrust.command, releaseRoot: record.root,
|
|
91
|
+
expectedManifestSha256: record.expectedManifestSha256, storeRoot: registry.storeRoot });
|
|
92
|
+
}),
|
|
93
|
+
settle: () => withWorkBuddyNativeServiceRecord(release.storeRoot, async registry => {
|
|
94
|
+
const record = pending(await registry?.read());
|
|
95
|
+
if (record) await registry.clearMcpTrustRequest(record);
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validate(record) {
|
|
101
|
+
const trust = record?.pendingMcpTrust;
|
|
102
|
+
if (trust !== undefined && (!trust || typeof trust !== "object" || Object.keys(trust).join() !== "command" || !path.isAbsolute(trust.command || "") || !/^node(?:\.exe)?$/i.test(path.basename(trust.command)))) throw failure("workbuddy_native_service_record_invalid");
|
|
103
|
+
if (record?.version !== 1|| !path.isAbsolute(record.root || "") || path.resolve(record.root) !== record.root || !/^[a-f0-9]{64}$/.test(record.expectedManifestSha256 || "") || !path.isAbsolute(record.nodePath || "") || !/^node(?:\.exe)?$/i.test(path.basename(record.nodePath)) || !/^HelloLynk WorkBuddy Native - [A-F0-9]{20} - [A-F0-9]{16}$/.test(record.taskName || "")) throw failure("workbuddy_native_service_record_invalid");
|
|
104
|
+
const endpoint = new URL(record.debugEndpoint);
|
|
105
|
+
if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.pathname !== "/" || endpoint.search || endpoint.hash || endpoint.username || endpoint.password || endpoint.origin !== record.debugEndpoint) throw failure("workbuddy_native_service_record_invalid");
|
|
106
|
+
}
|
|
107
|
+
async function safeParents(target) {
|
|
108
|
+
const root = path.parse(target).root;
|
|
109
|
+
let current = root;
|
|
110
|
+
for (const part of path.relative(root, target).split(path.sep).filter(Boolean)) {
|
|
111
|
+
current = path.join(current, part);
|
|
112
|
+
const stat = await fs.lstat(current);
|
|
113
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw failure("workbuddy_native_service_record_invalid");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import nativeTaskIndex from "../../../clients/core/hellolynk-native-task-index.cjs";
|
|
3
|
+
import { createNativeInboxRuntime } from "../../../clients/core/hellolynk-native-inbox-runtime.mjs";
|
|
4
|
+
import { readNativeReplyRoute } from "../../../clients/core/hellolynk-native-reply-routes.mjs";
|
|
5
|
+
import { createWorkBuddyNativeTaskClient } from "./native-task-cdp.mjs";
|
|
6
|
+
import { createWorkBuddyOriginLedger, importWorkBuddyOriginDrops, markWorkBuddyActiveScope } from "./origin-routes.mjs";
|
|
7
|
+
|
|
8
|
+
const { createNativeTaskIndex } = nativeTaskIndex;
|
|
9
|
+
const digest = value => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10
|
+
const failure = code => Object.assign(new Error(code), { code });
|
|
11
|
+
const REFUSED_TRUST = new Set(["workbuddy_native_mcp_config_mismatch", "workbuddy_native_mcp_disabled", "workbuddy_native_mcp_approval_failed"]);
|
|
12
|
+
const unsent = () => Object.assign(failure("workbuddy_native_not_submitted"), { nativeDispatchStarted: false });
|
|
13
|
+
const uuidFor = batchId => { const h = digest(batchId); return `${h.slice(0,8)}-${h.slice(8,12)}-${h.slice(12,16)}-${h.slice(16,20)}-${h.slice(20,32)}`; };
|
|
14
|
+
|
|
15
|
+
// The shared receiver owns batching, immediate delivery receipts and the one
|
|
16
|
+
// recovery retry. This bridge retains WorkBuddy's native trust and chat APIs.
|
|
17
|
+
export function startWorkBuddyNativeTaskWorker({ adapter, debugEndpoint = process.env.HELLOLYNK_WORKBUDDY_DEBUG_ENDPOINT || "http://127.0.0.1:9337", recoveryMs = 5_000,
|
|
18
|
+
acquireLease, readNoticeLocale, clientFactory = createWorkBuddyNativeTaskClient, storeFactory = openNativeTaskStore,
|
|
19
|
+
originInbox = null, readRoute = readNativeReplyRoute, importOrigins = importWorkBuddyOriginDrops, markScope = markWorkBuddyActiveScope, mcpTrust = null } = {}) {
|
|
20
|
+
if (!adapter || !Number.isSafeInteger(recoveryMs) || recoveryMs < 10) throw failure("workbuddy_native_worker_options_invalid");
|
|
21
|
+
let stopped = false, store, ledger, client, trustSettled = false, status = { status: "starting" };
|
|
22
|
+
// Settle the request once per service process (a new release starts a new
|
|
23
|
+
// one). Approval checks WorkBuddy's exact current entry and approves only
|
|
24
|
+
// if still untrusted, so trust the person gave first also settles it. A
|
|
25
|
+
// refusal (changed, disabled, declined) leaves trust to the person; trust
|
|
26
|
+
// the person withdraws afterwards is never reopened.
|
|
27
|
+
async function settleRequestedTrust(registration) {
|
|
28
|
+
const expected = await mcpTrust.take();
|
|
29
|
+
if (!expected) { trustSettled = true; return registration; }
|
|
30
|
+
if (stopped) return registration;
|
|
31
|
+
let approved;
|
|
32
|
+
try { approved = await client.approveHelloLynkMcp(expected); }
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (REFUSED_TRUST.has(error?.code)) { trustSettled = true; await mcpTrust.settle(); }
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
if (approved?.trusted === true) { trustSettled = true; await mcpTrust.settle(); }
|
|
38
|
+
return approved || registration;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const native = {
|
|
42
|
+
async beforeTick() {
|
|
43
|
+
client ||= clientFactory({ debugEndpoint, ownedSessionIds: [] });
|
|
44
|
+
if ((await client.probe())?.available !== true) throw failure("workbuddy_native_preload_unavailable");
|
|
45
|
+
},
|
|
46
|
+
async configureScope(scope) {
|
|
47
|
+
store = await storeFactory(scope); ledger = createWorkBuddyOriginLedger(scope);
|
|
48
|
+
if (originInbox) await markScope(originInbox, scope).catch(() => {});
|
|
49
|
+
},
|
|
50
|
+
async releaseScope() { await store?.close(); store = ledger = client = null; },
|
|
51
|
+
async legacyDelivery(item) {
|
|
52
|
+
const old = item.replyToClientMessageId ? await ledger.get(item.localMessageId) : store.getDelivery(digest(item.conversationKey), item.localMessageId);
|
|
53
|
+
return old ? /unknown|dispatching|creating/.test(old.status) ? "unknown" : "sent" : null;
|
|
54
|
+
},
|
|
55
|
+
legacyCarrier(key) { const old = store.getConversation(digest(key)); return old?.status === "created" ? old.nativeSessionId : null; },
|
|
56
|
+
async prepare(scope, state) {
|
|
57
|
+
client = clientFactory({ debugEndpoint, ownedSessionIds: [...new Set([...store.listSessions("created"), ...Object.values(state.carriers)])] });
|
|
58
|
+
let registration = await client.mcpRegistrationStatus({ connect: true });
|
|
59
|
+
if (mcpTrust && !trustSettled) registration = await settleRequestedTrust(registration);
|
|
60
|
+
const available = new Set((registration.tools || []).filter(t => t.enabled).map(t => t.name));
|
|
61
|
+
const allowed = registration.trusted === true && registration.enabled === true;
|
|
62
|
+
if (!allowed || !["hellolynk_claim_message", "hellolynk_send_authorized_message", "hellolynk_reply_authorized_message"].every(name => available.has(name)))
|
|
63
|
+
throw failure(allowed && registration.status != null && registration.status !== "ready" ? "workbuddy_native_mcp_not_ready" : "workbuddy_native_mcp_registration_required");
|
|
64
|
+
},
|
|
65
|
+
async listConversations(query = {}) {
|
|
66
|
+
if (query.targetConversationId) {
|
|
67
|
+
const id = query.targetConversationId, valid = /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,127}$/.test(id);
|
|
68
|
+
const current = valid ? await client.inspectOrigin(id).catch(() => null) : null;
|
|
69
|
+
const state = !valid ? 'deleted' : !current ? 'unavailable' : !current.present ? 'deleted' : current.archived ? 'archived' : current.busy ? 'active' : 'idle';
|
|
70
|
+
return { conversations: [{ targetConversationId: id, title: id, project: '', state }], coverage: 'partial', exactTargetSupported: true };
|
|
71
|
+
}
|
|
72
|
+
return client.listConversations();
|
|
73
|
+
},
|
|
74
|
+
async inspectThread(id) {
|
|
75
|
+
const current = await client.inspectOrigin(id);
|
|
76
|
+
if (typeof current?.present !== "boolean" || typeof current?.busy !== "boolean") throw failure("workbuddy_native_lifecycle_unknown");
|
|
77
|
+
return { state: !current.present ? "deleted" : current.archived ? "archived" : "present", thread: { status: current.busy ? "active" : "idle" } };
|
|
78
|
+
},
|
|
79
|
+
canNotifyOrigin(value) { return value?.state === "present" && value.thread?.status === "idle"; },
|
|
80
|
+
async call(operation, args, context) {
|
|
81
|
+
if (operation === "list_threads") { if ((await client.probe())?.available !== true) throw failure("workbuddy_native_preload_unavailable"); return {}; }
|
|
82
|
+
let id = args.threadId;
|
|
83
|
+
// All preparation precedes prompt submission. A failed create or route
|
|
84
|
+
// save may leave a carrier, but must not consume a native send attempt.
|
|
85
|
+
try {
|
|
86
|
+
if (operation === "create_thread") {
|
|
87
|
+
// WorkBuddy accepts a caller-chosen conversation ID. Recover a lost
|
|
88
|
+
// create receipt by checking this exact ID, without opening a window.
|
|
89
|
+
id = uuidFor(context.batchId);
|
|
90
|
+
let existing;
|
|
91
|
+
try { existing = await client.inspectOrigin(id); } catch { throw unsent(); }
|
|
92
|
+
if (!await context.stillCurrent()) throw unsent();
|
|
93
|
+
if (existing?.present !== true) await client.create({ nativeSessionId: id, title: "HelloLynk" });
|
|
94
|
+
else if (existing.archived) throw Object.assign(failure("workbuddy_native_carrier_closed"),
|
|
95
|
+
{ nativeDispatchStarted: false, nativeCarrierClosedBeforeDispatch: true });
|
|
96
|
+
await context.rememberThread(id);
|
|
97
|
+
}
|
|
98
|
+
if (!await context.stillCurrent()) throw unsent();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error?.nativeDispatchStarted === false) throw error;
|
|
101
|
+
throw Object.assign(failure(error?.code || "workbuddy_native_not_submitted"), { nativeDispatchStarted: false });
|
|
102
|
+
}
|
|
103
|
+
const result = await client.sendToIdle({ nativeSessionId: id, clientRequestId: `hellolynk-${context.batchId}`, text: args.prompt });
|
|
104
|
+
if (["busy", "missing", "archived", "lifecycle_unknown"].includes(result?.status)) throw unsent();
|
|
105
|
+
if (!["queued_unconfirmed", "submitted_unconfirmed"].includes(result?.status)) throw failure("workbuddy_native_dispatch_unknown");
|
|
106
|
+
status = { status: "delivered", nativeSessionId: id };
|
|
107
|
+
return { ok: true, threadId: id };
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
const runtime = createNativeInboxRuntime({ adapterId: "workbuddy", native, acquireLease, readNoticeLocale, pollMs: recoveryMs,
|
|
111
|
+
adapter: { nativeTaskScope: () => adapter.getNativeTaskScope(), inboxBatch: (...args) => adapter.inboxBatch(...args),
|
|
112
|
+
async listPendingMessages(input) {
|
|
113
|
+
// Import the send-origin hook before grouping replies by destination.
|
|
114
|
+
if (originInbox) await importOrigins(originInbox, await adapter.getNativeTaskScope()).catch(() => {});
|
|
115
|
+
return adapter.listPendingMessages(input);
|
|
116
|
+
} }, readReplyRoute: readRoute, onError: e => { if (e.operation !== "directory") status = { status: "unavailable", errorCode: e.code }; } });
|
|
117
|
+
const tick = () => runtime.tick().catch(error => { status = { status: "unavailable", errorCode: error.code || error.message }; });
|
|
118
|
+
const ready = tick();
|
|
119
|
+
runtime.start();
|
|
120
|
+
return Object.freeze({ ready, tick, get status() { return { ...status }; }, async open(id) {
|
|
121
|
+
if (!client) throw failure("workbuddy_native_task_not_owned"); return client.open(id);
|
|
122
|
+
}, async close() { stopped = true; await runtime.close(); } });
|
|
123
|
+
}
|
|
124
|
+
export async function openNativeTaskStore(scope) {
|
|
125
|
+
if (scope?.adapterId !== "workbuddy") throw failure("workbuddy_native_scope_invalid");
|
|
126
|
+
return createNativeTaskIndex(scope);
|
|
127
|
+
}
|