@huynle/hub 0.6.1 → 0.7.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/CHANGELOG.md +870 -0
- package/Dockerfile +33 -0
- package/README.md +1007 -66
- package/package.json +47 -2
- package/packages/claude-code/package.json +12 -0
- package/packages/claude-code/src/bridge.ts +79 -0
- package/packages/claude-code/src/daemon.ts +339 -0
- package/packages/claude-code/src/hook-input.ts +44 -0
- package/packages/claude-code/src/ids.ts +27 -0
- package/packages/claude-code/src/index.ts +18 -0
- package/packages/claude-code/src/launcher.ts +265 -0
- package/packages/claude-code/src/main.ts +127 -0
- package/packages/claude-code/src/mcp.ts +109 -0
- package/packages/claude-code/src/paths.ts +34 -0
- package/packages/claude-code/src/plugin.ts +99 -0
- package/packages/claude-code/src/references.ts +51 -0
- package/packages/claude-code/src/sessions.ts +600 -0
- package/packages/claude-code/src/tools.ts +66 -0
- package/packages/claude-code/src/wire.ts +71 -0
- package/packages/claude-code/src/wrapper.ts +156 -0
- package/packages/cli/dist/hub-cli.js +21647 -0
- package/packages/cli/package.json +11 -0
- package/packages/cli/src/acting.ts +70 -0
- package/packages/cli/src/args.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/archive-stream.ts +276 -0
- package/packages/cli/src/bootstrap/adapters/claude/README.md +63 -0
- package/packages/cli/src/bootstrap/adapters/claude/adapter.ts +250 -0
- package/packages/cli/src/bootstrap/adapters/claude/index.ts +42 -0
- package/packages/cli/src/bootstrap/adapters/claude/node.ts +99 -0
- package/packages/cli/src/bootstrap/adapters/claude/openpgp.ts +221 -0
- package/packages/cli/src/bootstrap/adapters/claude/ports.ts +44 -0
- package/packages/cli/src/bootstrap/adapters/claude/source.ts +160 -0
- package/packages/cli/src/bootstrap/adapters/claude/verify.ts +165 -0
- package/packages/cli/src/bootstrap/adapters/codex/archive.ts +103 -0
- package/packages/cli/src/bootstrap/adapters/codex/index.ts +60 -0
- package/packages/cli/src/bootstrap/adapters/codex/plan.ts +129 -0
- package/packages/cli/src/bootstrap/adapters/codex/sigstore.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/codex/source.ts +102 -0
- package/packages/cli/src/bootstrap/adapters/codex/verify.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/inflate.ts +433 -0
- package/packages/cli/src/bootstrap/adapters/node-services/control.ts +392 -0
- package/packages/cli/src/bootstrap/adapters/node-services/index.ts +23 -0
- package/packages/cli/src/bootstrap/adapters/node-services/launch.ts +223 -0
- package/packages/cli/src/bootstrap/adapters/node-services/plan.ts +222 -0
- package/packages/cli/src/bootstrap/adapters/node-services/render.ts +183 -0
- package/packages/cli/src/bootstrap/adapters/node-services/validate.ts +197 -0
- package/packages/cli/src/bootstrap/adapters/opencode/README.md +204 -0
- package/packages/cli/src/bootstrap/adapters/opencode/apply.ts +205 -0
- package/packages/cli/src/bootstrap/adapters/opencode/archive.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/opencode/config.ts +212 -0
- package/packages/cli/src/bootstrap/adapters/opencode/index.ts +81 -0
- package/packages/cli/src/bootstrap/adapters/opencode/plan.ts +266 -0
- package/packages/cli/src/bootstrap/adapters/opencode/verify.ts +208 -0
- package/packages/cli/src/bootstrap/adapters/readiness.ts +19 -0
- package/packages/cli/src/bootstrap/ansible/index.ts +2 -0
- package/packages/cli/src/bootstrap/ansible/render.ts +279 -0
- package/packages/cli/src/bootstrap/contracts/actions.ts +470 -0
- package/packages/cli/src/bootstrap/contracts/artifact.ts +323 -0
- package/packages/cli/src/bootstrap/contracts/canonical.ts +54 -0
- package/packages/cli/src/bootstrap/contracts/credentials.ts +108 -0
- package/packages/cli/src/bootstrap/contracts/discovery.ts +215 -0
- package/packages/cli/src/bootstrap/contracts/envelope.ts +143 -0
- package/packages/cli/src/bootstrap/contracts/exec.ts +74 -0
- package/packages/cli/src/bootstrap/contracts/harness.ts +202 -0
- package/packages/cli/src/bootstrap/contracts/index.ts +28 -0
- package/packages/cli/src/bootstrap/contracts/journal.ts +198 -0
- package/packages/cli/src/bootstrap/contracts/paths.ts +91 -0
- package/packages/cli/src/bootstrap/contracts/plan.ts +155 -0
- package/packages/cli/src/bootstrap/contracts/platform.ts +62 -0
- package/packages/cli/src/bootstrap/contracts/preview.ts +190 -0
- package/packages/cli/src/bootstrap/contracts/profile.ts +105 -0
- package/packages/cli/src/bootstrap/contracts/secrets.ts +52 -0
- package/packages/cli/src/bootstrap/contracts/validate.ts +212 -0
- package/packages/cli/src/bootstrap/discovery/discover.ts +146 -0
- package/packages/cli/src/bootstrap/discovery/executables.ts +134 -0
- package/packages/cli/src/bootstrap/discovery/host.ts +96 -0
- package/packages/cli/src/bootstrap/discovery/index.ts +6 -0
- package/packages/cli/src/bootstrap/discovery/node-probe.ts +97 -0
- package/packages/cli/src/bootstrap/discovery/probe.ts +41 -0
- package/packages/cli/src/bootstrap/discovery/util.ts +13 -0
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +226 -0
- package/packages/cli/src/bootstrap/enrollment/index.ts +7 -0
- package/packages/cli/src/bootstrap/enrollment/policy.ts +54 -0
- package/packages/cli/src/bootstrap/enrollment/result.ts +63 -0
- package/packages/cli/src/bootstrap/enrollment/status.ts +119 -0
- package/packages/cli/src/bootstrap/enrollment/store.ts +173 -0
- package/packages/cli/src/bootstrap/preview/index.ts +5 -0
- package/packages/cli/src/bootstrap/preview/links.ts +31 -0
- package/packages/cli/src/bootstrap/preview/plan.ts +242 -0
- package/packages/cli/src/bootstrap/preview/preview.ts +104 -0
- package/packages/cli/src/bootstrap/preview/render.ts +30 -0
- package/packages/cli/src/bootstrap/profiles/audit.ts +87 -0
- package/packages/cli/src/bootstrap/profiles/catalog.ts +49 -0
- package/packages/cli/src/bootstrap/profiles/desired.ts +96 -0
- package/packages/cli/src/bootstrap/profiles/drift.ts +286 -0
- package/packages/cli/src/bootstrap/profiles/history.ts +263 -0
- package/packages/cli/src/bootstrap/profiles/index.ts +11 -0
- package/packages/cli/src/bootstrap/profiles/upgrade.ts +276 -0
- package/packages/cli/src/bootstrap/profiles/versions.ts +50 -0
- package/packages/cli/src/bootstrap/release/README.md +115 -0
- package/packages/cli/src/bootstrap/release/assets.ts +39 -0
- package/packages/cli/src/bootstrap/release/cli.ts +332 -0
- package/packages/cli/src/bootstrap/release/harness-pins.json +94 -0
- package/packages/cli/src/bootstrap/release/manifest.ts +118 -0
- package/packages/cli/src/bootstrap/release/pins.ts +192 -0
- package/packages/cli/src/bootstrap/release/text-assets.d.ts +9 -0
- package/packages/cli/src/bootstrap/release/trust-root.json +13 -0
- package/packages/cli/src/bootstrap/release/trust-root.ts +39 -0
- package/packages/cli/src/bootstrap/release/verify.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/builtin.ts +312 -0
- package/packages/cli/src/bootstrap/transaction/engine.ts +994 -0
- package/packages/cli/src/bootstrap/transaction/executor.ts +112 -0
- package/packages/cli/src/bootstrap/transaction/fs.ts +194 -0
- package/packages/cli/src/bootstrap/transaction/index.ts +12 -0
- package/packages/cli/src/bootstrap/transaction/json-merge.ts +403 -0
- package/packages/cli/src/bootstrap/transaction/lock.ts +132 -0
- package/packages/cli/src/bootstrap/transaction/resolve.ts +48 -0
- package/packages/cli/src/bootstrap/transaction/result.ts +60 -0
- package/packages/cli/src/bootstrap/transaction/state.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/store.ts +189 -0
- package/packages/cli/src/bootstrap/transaction/stream.ts +254 -0
- package/packages/cli/src/cli.ts +814 -0
- package/packages/cli/src/credentials.ts +75 -0
- package/packages/cli/src/decisions.ts +106 -0
- package/packages/cli/src/format.ts +185 -0
- package/packages/cli/src/grants.ts +105 -0
- package/packages/cli/src/http.ts +86 -0
- package/packages/cli/src/launches.ts +157 -0
- package/packages/cli/src/login.ts +160 -0
- package/packages/cli/src/main.ts +63 -0
- package/packages/cli/src/mcp.ts +879 -0
- package/packages/cli/src/messaging.ts +108 -0
- package/packages/cli/src/node.ts +279 -0
- package/packages/cli/src/openshell.ts +166 -0
- package/packages/cli/src/setup/commands.ts +809 -0
- package/packages/cli/src/setup/contributors.ts +171 -0
- package/packages/cli/src/setup/dispatch.ts +74 -0
- package/packages/cli/src/setup/launch-driver.ts +109 -0
- package/packages/cli/src/setup/runtime.ts +456 -0
- package/packages/cli/src/setup/spoke-probe.ts +120 -0
- package/packages/cli/src/setup/state.ts +244 -0
- package/packages/cli/src/setup/user-bin.ts +39 -0
- package/packages/cli/src/spoke-services.ts +109 -0
- package/packages/cli/src/watch.ts +28 -0
- package/packages/codex/package.json +12 -0
- package/packages/codex/src/app-server.ts +44 -0
- package/packages/codex/src/ids.ts +28 -0
- package/packages/codex/src/index.ts +11 -0
- package/packages/codex/src/jsonrpc.ts +168 -0
- package/packages/codex/src/main.ts +123 -0
- package/packages/codex/src/paths.ts +65 -0
- package/packages/codex/src/sessions.ts +578 -0
- package/packages/codex/src/spoke.ts +252 -0
- package/packages/codex/src/stdio.ts +101 -0
- package/packages/codex/src/websocket.ts +206 -0
- package/packages/codex/src/wrapper.ts +104 -0
- package/packages/node/package.json +12 -0
- package/packages/node/src/config.ts +184 -0
- package/packages/node/src/connection.ts +283 -0
- package/packages/node/src/daemon.ts +281 -0
- package/packages/node/src/git.ts +107 -0
- package/packages/node/src/harnesses.ts +95 -0
- package/packages/node/src/host.ts +319 -0
- package/packages/node/src/index.ts +17 -0
- package/packages/node/src/main.ts +11 -0
- package/packages/node/src/openshell.ts +73 -0
- package/packages/node/src/run.ts +106 -0
- package/packages/preset/package.json +12 -0
- package/packages/preset/presets/aider.json +8 -0
- package/packages/preset/presets/goose.json +8 -0
- package/packages/preset/src/index.ts +2 -0
- package/packages/preset/src/main.ts +195 -0
- package/packages/preset/src/presets.ts +87 -0
- package/packages/preset/src/spoke.ts +251 -0
- package/packages/preset/src/tmux.ts +160 -0
- package/packages/protocol/package.json +12 -0
- package/packages/protocol/src/harness-events.ts +207 -0
- package/packages/protocol/src/index.ts +1215 -0
- package/packages/protocol/src/launch.ts +172 -0
- package/packages/protocol/src/node.ts +409 -0
- package/packages/protocol/src/preset.ts +144 -0
- package/packages/protocol/src/webhook.ts +62 -0
- package/packages/protocol/src/wire.ts +54 -0
- package/packages/runtime-openshell/image/Dockerfile +36 -0
- package/packages/runtime-openshell/image/README.md +78 -0
- package/packages/runtime-openshell/image/build.ts +203 -0
- package/packages/runtime-openshell/image/entry.sh +163 -0
- package/packages/runtime-openshell/image/image.ts +119 -0
- package/packages/runtime-openshell/image/start.sh +24 -0
- package/packages/runtime-openshell/package.json +12 -0
- package/packages/runtime-openshell/src/cli.ts +584 -0
- package/packages/runtime-openshell/src/index.ts +6 -0
- package/packages/runtime-openshell/src/policy.ts +155 -0
- package/packages/runtime-openshell/src/profiles.ts +181 -0
- package/packages/runtime-openshell/src/runtime.ts +475 -0
- package/packages/runtime-sdk/package.json +12 -0
- package/packages/runtime-sdk/src/index.ts +87 -0
- package/packages/sdk/package.json +12 -0
- package/packages/sdk/src/address.ts +25 -0
- package/packages/sdk/src/client.ts +553 -0
- package/packages/sdk/src/envelope.ts +108 -0
- package/packages/sdk/src/errors.ts +107 -0
- package/packages/sdk/src/index.ts +10 -0
- package/packages/sdk/src/sse.ts +70 -0
- package/packages/sdk/src/types.ts +517 -0
- package/packages/sdk/src/webhook.ts +102 -0
- package/packages/spoke-sdk/package.json +12 -0
- package/packages/spoke-sdk/src/activity.ts +3 -0
- package/packages/spoke-sdk/src/connection.ts +295 -0
- package/packages/spoke-sdk/src/delivery.ts +119 -0
- package/packages/spoke-sdk/src/enroll.ts +101 -0
- package/packages/spoke-sdk/src/harness-hello.ts +79 -0
- package/packages/spoke-sdk/src/harness-spoke.ts +263 -0
- package/packages/spoke-sdk/src/index.ts +12 -0
- package/packages/spoke-sdk/src/prompt-refs.ts +154 -0
- package/packages/spoke-sdk/src/prompt-source.ts +8 -0
- package/packages/spoke-sdk/src/session-ids.ts +49 -0
- package/packages/spoke-sdk/src/session-tokens.ts +78 -0
- package/scripts/hub-claude-code.service +34 -0
- package/scripts/hub-codex-spoke.service +34 -0
- package/scripts/hub-node.service +35 -0
- package/scripts/io.github.huynle.hub-claude-code.plist +53 -0
- package/scripts/io.github.huynle.hub-codex-spoke.plist +53 -0
- package/scripts/io.github.huynle.hub-node.plist +52 -0
- package/src/central/actions.ts +233 -0
- package/src/central/agent/acting-routes.ts +133 -0
- package/src/central/agent/acting-switch.ts +93 -0
- package/src/central/agent/acting.ts +599 -0
- package/src/central/agent/actor.ts +193 -0
- package/src/central/agent/app-tokens.ts +152 -0
- package/src/central/agent/archive-inactive.ts +66 -0
- package/src/central/agent/audit.ts +185 -0
- package/src/central/agent/decisions.ts +530 -0
- package/src/central/agent/delivery.ts +678 -0
- package/src/central/agent/directory-routes.ts +328 -0
- package/src/central/agent/directory.ts +473 -0
- package/src/central/agent/envelope.ts +51 -0
- package/src/central/agent/errors.ts +74 -0
- package/src/central/agent/escalation.ts +219 -0
- package/src/central/agent/event-cursor.ts +49 -0
- package/src/central/agent/event-writers.ts +181 -0
- package/src/central/agent/events-routes.ts +148 -0
- package/src/central/agent/events.ts +277 -0
- package/src/central/agent/grant-requests.ts +221 -0
- package/src/central/agent/grant-routes.ts +70 -0
- package/src/central/agent/grants.ts +235 -0
- package/src/central/agent/launch-engine.ts +1025 -0
- package/src/central/agent/launch-routes.ts +252 -0
- package/src/central/agent/launch-spec.ts +289 -0
- package/src/central/agent/launch-store.ts +264 -0
- package/src/central/agent/launch-tokens.ts +206 -0
- package/src/central/agent/message-limits.ts +123 -0
- package/src/central/agent/message-routes.ts +385 -0
- package/src/central/agent/messages.ts +442 -0
- package/src/central/agent/names.ts +86 -0
- package/src/central/agent/placement.ts +419 -0
- package/src/central/agent/policy.ts +378 -0
- package/src/central/agent/read-routes.ts +385 -0
- package/src/central/agent/routes.ts +281 -0
- package/src/central/agent/session-tokens.ts +227 -0
- package/src/central/agent/versioned.ts +26 -0
- package/src/central/agent/watch.ts +112 -0
- package/src/central/agent/webhook-routes.ts +86 -0
- package/src/central/agent/webhooks.ts +493 -0
- package/src/central/agent-data.ts +177 -0
- package/src/central/auth.ts +350 -121
- package/src/central/backend/file-state.ts +240 -0
- package/src/central/backend/index.ts +116 -0
- package/src/central/backend/memory-bus.ts +93 -0
- package/src/central/backend/memory-catalog.ts +90 -0
- package/src/central/backend/memory-counters.ts +102 -0
- package/src/central/backend/memory-events.ts +45 -0
- package/src/central/backend/memory-kv.ts +38 -0
- package/src/central/backend/memory-state.ts +77 -0
- package/src/central/backend/postgres-migrate.ts +100 -0
- package/src/central/backend/postgres-state.ts +197 -0
- package/src/central/backend/types.ts +246 -0
- package/src/central/backend/valkey-bus.ts +425 -0
- package/src/central/backend/valkey-catalog.ts +190 -0
- package/src/central/backend/valkey-client.ts +36 -0
- package/src/central/backend/valkey-counters.ts +38 -0
- package/src/central/backend/valkey-events.ts +168 -0
- package/src/central/backend/valkey-kv.ts +54 -0
- package/src/central/backend/valkey-locks.ts +92 -0
- package/src/central/cluster/directory.ts +141 -0
- package/src/central/cluster/feed.ts +84 -0
- package/src/central/cluster/leader.ts +232 -0
- package/src/central/cluster/live.ts +151 -0
- package/src/central/cluster/recommend.ts +59 -0
- package/src/central/cluster/rpc.ts +150 -0
- package/src/central/cluster/view.ts +232 -0
- package/src/central/config.ts +197 -3
- package/src/central/done.ts +96 -0
- package/src/central/events-log.ts +100 -0
- package/src/central/http.ts +1149 -170
- package/src/central/import.ts +318 -0
- package/src/central/main.ts +211 -12
- package/src/central/mcp-http.ts +116 -0
- package/src/central/nodes.ts +314 -0
- package/src/central/oidc.ts +34 -14
- package/src/central/push-target.ts +118 -18
- package/src/central/push.ts +256 -82
- package/src/central/registry.ts +902 -92
- package/src/central/say-stream.ts +128 -0
- package/src/central/session-token-push.ts +233 -0
- package/src/central/speech/openai.ts +21 -14
- package/src/central/speech/openrouter.ts +27 -10
- package/src/central/speech/provider.ts +54 -0
- package/src/central/speech/service.ts +22 -7
- package/src/central/spoke-ws.ts +522 -44
- package/src/central/store-shared.ts +518 -0
- package/src/central/store.ts +625 -189
- package/src/central/token-keys.ts +165 -0
- package/src/config.ts +118 -7
- package/src/index.ts +195 -43
- package/src/protocol.ts +3 -650
- package/src/rpc.ts +27 -2
- package/src/spoke/client.ts +3 -272
- package/src/spoke/conversation.ts +16 -12
- package/src/spoke/deliver.ts +75 -0
- package/src/spoke/enroll.ts +2 -79
- package/src/spoke/env-inject.ts +160 -0
- package/src/spoke/hub-rpc.ts +115 -0
- package/src/spoke/hub-tool.ts +409 -0
- package/src/spoke/launch-source.ts +22 -0
- package/src/spoke/launchers.ts +174 -0
- package/src/spoke/locations.ts +3 -2
- package/src/spoke/node-roots.ts +61 -0
- package/src/spoke/prompt-refs.ts +116 -0
- package/src/spoke/session-tokens.ts +2 -0
- package/src/spoke/spoke.ts +465 -28
- package/src/spoke/taskboard.ts +243 -0
- package/src/spoke/timeline.ts +184 -6
- package/src/spoke/tracker.ts +79 -4
- package/src/status.ts +203 -10
- package/src/tui-commands.ts +13 -6
- package/src/tui-messaging.ts +93 -0
- package/src/tui.ts +15 -0
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/highlight-y4rJE6Tt.js +5 -0
- package/web/dist/assets/index-DWnMhXk9.js +20 -0
- package/web/dist/assets/index-DmUXEnw9.css +1 -0
- package/web/dist/assets/ort-wasm-simd-threaded-CxTQ5xH-.wasm +0 -0
- package/web/dist/assets/silero-worker-C8-8vLVS.js +11 -0
- package/web/dist/assets/silero_vad-Dt9-bTDn.onnx +0 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/icon-192.png +0 -0
- package/web/dist/icon-512.png +0 -0
- package/web/dist/icon-maskable-512.png +0 -0
- package/web/dist/icon.svg +5 -5
- package/web/dist/index.html +17 -4
- package/web/dist/manifest.webmanifest +2 -2
- package/web/dist/sw.js +79 -15
- package/web/dist/assets/index-DClBvfLL.css +0 -1
- package/web/dist/assets/index-Djo_yF0a.js +0 -12
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The machine's node workspace roots, for launch creates (feature "launch"): a launch's session may start in a new
|
|
2
|
+
// workspace (a worktree or clone `hub node` just prepared) the spoke never reported, but only under a root.
|
|
3
|
+
// node.json is owned by `hub node` (packages/node): {workspaceRoots: string[], labels?, slots?, memoryMB?}. The spoke
|
|
4
|
+
// reads it, read-only and only workspaceRoots, from next to its own config (configPath: ~/.config/hub, or the
|
|
5
|
+
// directory of $HUB_SPOKE_CONFIG; a launch-bound spoke: the directory of its launch config).
|
|
6
|
+
import { readFileSync, realpathSync, statSync } from "node:fs"
|
|
7
|
+
import { homedir } from "node:os"
|
|
8
|
+
import path from "node:path"
|
|
9
|
+
import { configPath } from "../config"
|
|
10
|
+
import type { Env } from "../env"
|
|
11
|
+
|
|
12
|
+
export const NODE_CONFIG_FILE = "node.json"
|
|
13
|
+
|
|
14
|
+
/** node.json next to the spoke config file. */
|
|
15
|
+
export function nodeConfigPath(env: Env = process.env, home: string = homedir()): string {
|
|
16
|
+
return nodeConfigPathNextTo(configPath(env, home))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** node.json in the directory of `configFile` (a launch-bound spoke: next to its launch config). */
|
|
20
|
+
export function nodeConfigPathNextTo(configFile: string): string {
|
|
21
|
+
return path.join(path.dirname(configFile), NODE_CONFIG_FILE)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The absolute workspace roots in a node.json; none when the file is missing or unreadable. */
|
|
25
|
+
export function readWorkspaceRoots(file: string): string[] {
|
|
26
|
+
let parsed: unknown
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(readFileSync(file, "utf8"))
|
|
29
|
+
} catch {
|
|
30
|
+
return []
|
|
31
|
+
}
|
|
32
|
+
const roots = (parsed as { workspaceRoots?: unknown } | null)?.workspaceRoots
|
|
33
|
+
if (!Array.isArray(roots)) return []
|
|
34
|
+
return roots.filter((r): r is string => typeof r === "string" && r.length > 0 && path.isAbsolute(r))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function real(p: string): string | undefined {
|
|
38
|
+
try {
|
|
39
|
+
return realpathSync(p)
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const within = (dir: string, root: string) => dir === root || dir.startsWith(root.endsWith(path.sep) ? root : root + path.sep)
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Why `directory` cannot host a launch session, or undefined when it is an existing directory at or under one of
|
|
49
|
+
* `roots`. Both sides are compared by real path, so a symlink cannot lead out of a root.
|
|
50
|
+
*/
|
|
51
|
+
export function launchDirectoryProblem(directory: string, roots: readonly string[]): string | undefined {
|
|
52
|
+
if (roots.length === 0) return "This machine has no node workspace roots"
|
|
53
|
+
const dir = real(directory)
|
|
54
|
+
if (!dir) return `Directory ${directory} does not exist`
|
|
55
|
+
if (!statSync(dir).isDirectory()) return `${directory} is not a directory`
|
|
56
|
+
for (const root of roots) {
|
|
57
|
+
const r = real(root)
|
|
58
|
+
if (r && within(dir, r)) return undefined
|
|
59
|
+
}
|
|
60
|
+
return `Directory ${directory} is not under the node workspace roots`
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Prompt references (design §2.4): hub addresses typed into a prompt get context, never routing. The pure parts
|
|
2
|
+
// (find, resolve, render) live in the spoke SDK; the OpenCode prompt hook stays here.
|
|
3
|
+
import { HubClient } from "../../packages/sdk/src/index.ts"
|
|
4
|
+
import { findReferences, renderReferences, resolveReferences, type Mention, type ReferenceSnapshot } from "../../packages/spoke-sdk/src/prompt-refs.ts"
|
|
5
|
+
import { isHubPrompt } from "./conversation"
|
|
6
|
+
import type { HubToolSpoke } from "./hub-tool"
|
|
7
|
+
|
|
8
|
+
export * from "../../packages/spoke-sdk/src/prompt-refs.ts"
|
|
9
|
+
|
|
10
|
+
/** The longest a prompt waits for the hub (design §2.4: ≤ 1.5 s); then it goes on without context. */
|
|
11
|
+
export const PROMPT_REF_TIMEOUT_MS = 1500
|
|
12
|
+
/** How long one session's lookup (or failure) is reused. */
|
|
13
|
+
export const PROMPT_REF_TTL_MS = 10_000
|
|
14
|
+
/** Cached sessions at most (expired ones are dropped first). */
|
|
15
|
+
const CACHE_MAX = 200
|
|
16
|
+
|
|
17
|
+
export const REFERENCE_FILE_NAME = "hub-references.txt"
|
|
18
|
+
const REFERENCE_FILE_DESCRIPTION = "hub: the sessions this prompt's @addresses name (context from the hub plugin)"
|
|
19
|
+
|
|
20
|
+
/** OpenCode's prompt hook event (plugin SessionPrompt), the parts used here. */
|
|
21
|
+
export interface PromptEvent {
|
|
22
|
+
readonly sessionID: string
|
|
23
|
+
readonly messageID?: string
|
|
24
|
+
readonly delivery?: string
|
|
25
|
+
metadata?: Record<string, unknown>
|
|
26
|
+
prompt: {
|
|
27
|
+
text: string
|
|
28
|
+
files?: { uri: string; name?: string; description?: string; mention?: Mention }[]
|
|
29
|
+
agents?: { name: string; mention?: Mention }[]
|
|
30
|
+
skills?: { name?: string; mention?: Mention }[]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PromptReferencesOptions {
|
|
35
|
+
readonly timeoutMs?: number
|
|
36
|
+
readonly ttlMs?: number
|
|
37
|
+
readonly now?: () => number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The hook: adds hub context for @name / @@role addresses as one text file; never changes the prompt text. */
|
|
41
|
+
export class PromptReferences {
|
|
42
|
+
readonly #spoke: () => HubToolSpoke | undefined
|
|
43
|
+
readonly #timeoutMs: number
|
|
44
|
+
readonly #ttlMs: number
|
|
45
|
+
readonly #now: () => number
|
|
46
|
+
readonly #cache = new Map<string, { at: number; snapshot: Promise<ReferenceSnapshot | undefined> }>()
|
|
47
|
+
|
|
48
|
+
constructor(spoke: () => HubToolSpoke | undefined, options: PromptReferencesOptions = {}) {
|
|
49
|
+
this.#spoke = spoke
|
|
50
|
+
this.#timeoutMs = Math.min(options.timeoutMs ?? PROMPT_REF_TIMEOUT_MS, PROMPT_REF_TIMEOUT_MS)
|
|
51
|
+
this.#ttlMs = options.ttlMs ?? PROMPT_REF_TTL_MS
|
|
52
|
+
this.#now = options.now ?? Date.now
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Never throws and never waits longer than the timeout (fail open). */
|
|
56
|
+
async onPrompt(event: PromptEvent): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const prompt = event?.prompt
|
|
59
|
+
if (!prompt || typeof prompt.text !== "string" || isHubPrompt(event.metadata)) return
|
|
60
|
+
const addresses = findReferences(prompt)
|
|
61
|
+
if (!addresses.length) return
|
|
62
|
+
const spoke = this.#spoke()
|
|
63
|
+
const identity = spoke?.shellIdentity(event.sessionID)
|
|
64
|
+
if (!spoke || !identity?.token || !identity.url) return
|
|
65
|
+
const { token, url } = identity
|
|
66
|
+
const snapshot = await this.#snapshot(event.sessionID, () => this.#fetch({ token, url }, spoke.hubSupports("messaging")))
|
|
67
|
+
if (!snapshot) return
|
|
68
|
+
const refs = resolveReferences(addresses, snapshot, event.sessionID)
|
|
69
|
+
if (!refs.length) return
|
|
70
|
+
prompt.files = [...(prompt.files ?? []), referenceFile(renderReferences(refs))]
|
|
71
|
+
} catch {
|
|
72
|
+
// Fail open: the prompt goes on without context.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#snapshot(sessionID: string, load: () => Promise<ReferenceSnapshot | undefined>): Promise<ReferenceSnapshot | undefined> {
|
|
77
|
+
const now = this.#now()
|
|
78
|
+
const cached = this.#cache.get(sessionID)
|
|
79
|
+
if (cached && now - cached.at <= this.#ttlMs) return cached.snapshot
|
|
80
|
+
for (const [key, value] of this.#cache) if (now - value.at > this.#ttlMs) this.#cache.delete(key)
|
|
81
|
+
// Still full: drop the oldest (Map keeps insertion order).
|
|
82
|
+
while (this.#cache.size >= CACHE_MAX) this.#cache.delete(this.#cache.keys().next().value!)
|
|
83
|
+
const snapshot = load().catch(() => undefined)
|
|
84
|
+
this.#cache.set(sessionID, { at: now, snapshot })
|
|
85
|
+
return snapshot
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** who (required), status and inbox (optional details), in parallel, all within the timeout. */
|
|
89
|
+
async #fetch(identity: { token: string; url: string }, messaging: boolean): Promise<ReferenceSnapshot | undefined> {
|
|
90
|
+
const client = new HubClient({ url: identity.url, token: identity.token })
|
|
91
|
+
const abort = new AbortController()
|
|
92
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
93
|
+
const deadline = new Promise<undefined>((resolve) => {
|
|
94
|
+
timer = setTimeout(() => {
|
|
95
|
+
abort.abort()
|
|
96
|
+
resolve(undefined)
|
|
97
|
+
}, this.#timeoutMs)
|
|
98
|
+
})
|
|
99
|
+
const signal = { signal: abort.signal }
|
|
100
|
+
const optional = <T>(promise: Promise<T>) => promise.catch(() => undefined)
|
|
101
|
+
const lookup = Promise.all([client.who(signal), optional(client.status(signal)), messaging ? optional(client.inbox(signal)) : undefined]).then(
|
|
102
|
+
([who, status, inbox]): ReferenceSnapshot => ({ who: who.sessions, ...(status ? { status } : {}), ...(inbox ? { waiting: inbox.waiting } : {}) }),
|
|
103
|
+
)
|
|
104
|
+
try {
|
|
105
|
+
return await Promise.race([lookup, deadline])
|
|
106
|
+
} finally {
|
|
107
|
+
clearTimeout(timer)
|
|
108
|
+
lookup.catch(() => undefined)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The context as an inline text file (the spike's mechanism: the model sees it inside the same user message). */
|
|
114
|
+
export function referenceFile(text: string): { uri: string; name: string; description: string } {
|
|
115
|
+
return { uri: `data:text/plain;base64,${Buffer.from(text, "utf8").toString("base64")}`, name: REFERENCE_FILE_NAME, description: REFERENCE_FILE_DESCRIPTION }
|
|
116
|
+
}
|