@amoree-code/ocean 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +284 -2
- package/dist/application/auth/auth-orchestrator.js +77 -0
- package/dist/application/clients/clients.js +646 -0
- package/dist/application/clients/editors.js +398 -0
- package/dist/application/clients/hooks.js +335 -0
- package/dist/application/clients/native.js +410 -0
- package/dist/application/clients/opencode-plugin.js +158 -0
- package/dist/application/clients/registry.js +188 -0
- package/dist/application/gateway/webhook-gateway.js +137 -0
- package/dist/application/install/provider-installer.js +184 -0
- package/dist/application/integrations/charter-copies.js +76 -0
- package/dist/application/integrations/claude-vscode.js +106 -0
- package/dist/application/integrations/engine-entry-repair.js +105 -0
- package/dist/application/integrations/skill-filter.js +254 -0
- package/dist/application/layout/layout-apply.js +443 -0
- package/dist/application/layout/layout-plan.js +236 -0
- package/dist/application/layout/workspace-template.js +34 -0
- package/dist/application/onboarding/state.js +23 -0
- package/dist/application/onboarding/steps.js +249 -0
- package/dist/application/profiles/profile-distribution.js +53 -0
- package/dist/application/profiles/profile-suggestion.js +39 -0
- package/dist/application/profiles/profile-upgrade-service.js +208 -0
- package/dist/application/pull/history-index.js +476 -0
- package/dist/application/pull/project-match.js +132 -0
- package/dist/application/pull/pull.js +202 -0
- package/dist/application/pull/server-capture.js +307 -0
- package/dist/application/scheduler/local-scheduler.js +210 -0
- package/dist/composition/runtime.js +64 -0
- package/dist/engine-root.js +9 -0
- package/dist/infrastructure/filesystem/profile-loader.js +46 -0
- package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
- package/dist/infrastructure/filesystem/skill-loader.js +84 -0
- package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
- package/dist/infrastructure/observability/runtime-logger.js +16 -0
- package/dist/infrastructure/process/agent-tool-executor.js +99 -0
- package/dist/infrastructure/process/cli-process.js +49 -0
- package/dist/infrastructure/process/interactive-process.js +145 -0
- package/dist/infrastructure/process/process-supervisor.js +194 -0
- package/dist/infrastructure/process/service.js +252 -0
- package/dist/infrastructure/providers/browser-provider.js +2 -0
- package/dist/infrastructure/providers/claude-sdk.js +137 -0
- package/dist/infrastructure/providers/client-home.js +14 -0
- package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
- package/dist/infrastructure/providers/ollama-embedder.js +74 -0
- package/dist/infrastructure/providers/openai-compatible.js +268 -0
- package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
- package/dist/infrastructure/providers/provider-capabilities.js +50 -0
- package/dist/infrastructure/providers/provider-invocation.js +184 -0
- package/dist/infrastructure/providers/provider-registry.js +77 -0
- package/dist/infrastructure/providers/providers.js +134 -0
- package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
- package/dist/interfaces/cli/auth-command.js +54 -0
- package/dist/interfaces/cli/browser-bridge-command.js +93 -0
- package/dist/interfaces/cli/browser-command.js +119 -0
- package/dist/interfaces/cli/capture-command.js +125 -0
- package/dist/interfaces/cli/client-test-command.js +133 -0
- package/dist/interfaces/cli/clients-command.js +129 -0
- package/dist/interfaces/cli/command-registry.js +1211 -0
- package/dist/interfaces/cli/context-command.js +172 -0
- package/dist/interfaces/cli/daily-command.js +131 -0
- package/dist/interfaces/cli/governance-command.js +55 -0
- package/dist/interfaces/cli/handoff-command.js +35 -0
- package/dist/interfaces/cli/help-command.js +207 -0
- package/dist/interfaces/cli/idea-command.js +47 -0
- package/dist/interfaces/cli/intercept-command.js +317 -0
- package/dist/interfaces/cli/layout-command.js +27 -0
- package/dist/interfaces/cli/market-command.js +52 -0
- package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
- package/dist/interfaces/cli/memory-command.js +317 -0
- package/dist/interfaces/cli/migrate-command.js +26 -0
- package/dist/interfaces/cli/observe-command.js +78 -0
- package/dist/interfaces/cli/onboard-command.js +123 -0
- package/dist/interfaces/cli/operate-command.js +59 -0
- package/dist/interfaces/cli/profiles-command.js +70 -0
- package/dist/interfaces/cli/pull-command.js +54 -0
- package/dist/interfaces/cli/setup-command.js +143 -0
- package/dist/interfaces/cli/tasks-command.js +249 -0
- package/dist/main.js +49 -0
- package/dist/node-warnings.js +17 -0
- package/dist/version.js +8 -0
- package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
- package/market/mcp/gateway/ocean.capability.json +21 -0
- package/market/mcp/obsidian/ocean.capability.json +21 -0
- package/market/mcp/ocean/ocean.capability.json +21 -0
- package/market/mcp/playwright/ocean.capability.json +23 -0
- package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
- package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
- package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
- package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
- package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
- package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
- package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
- package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
- package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
- package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
- package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
- package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
- package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
- package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
- package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
- package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
- package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
- package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
- package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
- package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
- package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
- package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
- package/node_modules/@ocean/brain/dist/memory/signal-detection.js +66 -0
- package/node_modules/@ocean/brain/package.json +11 -0
- package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
- package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
- package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
- package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
- package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
- package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
- package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
- package/node_modules/@ocean/context/package.json +11 -0
- package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
- package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
- package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
- package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
- package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
- package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
- package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
- package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
- package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
- package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
- package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
- package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
- package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
- package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
- package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
- package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
- package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
- package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
- package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
- package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
- package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
- package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
- package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
- package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
- package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
- package/node_modules/@ocean/core/package.json +14 -0
- package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
- package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
- package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
- package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
- package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
- package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
- package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
- package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
- package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
- package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
- package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
- package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
- package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
- package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
- package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
- package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
- package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
- package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
- package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
- package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
- package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
- package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
- package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
- package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
- package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
- package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
- package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
- package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
- package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
- package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
- package/node_modules/@ocean/loop/package.json +11 -0
- package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
- package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
- package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
- package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
- package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
- package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
- package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
- package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
- package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
- package/node_modules/@ocean/market/dist/install/lock.js +34 -0
- package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
- package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
- package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
- package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
- package/node_modules/@ocean/market/dist/security/sandbox.js +74 -0
- package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
- package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
- package/node_modules/@ocean/market/dist/skills/skill-curation.js +123 -0
- package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
- package/node_modules/@ocean/market/dist/skills/task-observer.js +165 -0
- package/node_modules/@ocean/market/package.json +11 -0
- package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
- package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
- package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
- package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
- package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
- package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
- package/node_modules/@ocean/services/package.json +11 -0
- package/ocean-marketplace.json +415 -0
- package/package.json +67 -4
- package/skills/core/business-logic/SKILL.md +22 -0
- package/skills/core/catch-up/SKILL.md +17 -0
- package/skills/core/core-thinking/SKILL.md +11 -0
- package/skills/core/design-thinking/SKILL.md +19 -0
- package/skills/core/graft/SKILL.md +156 -0
- package/skills/core/loop/SKILL.md +20 -0
- package/skills/core/session-handoff/SKILL.md +15 -0
- package/skills/core/use-browser/SKILL.md +69 -0
- package/skills/core/verification/SKILL.md +11 -0
- package/skills/index.json +56 -0
- package/templates/README.md +4 -0
- package/templates/profiles/default.json +51 -0
- package/templates/profiles/developer.json +51 -0
- package/templates/profiles/reviewer.json +51 -0
- package/templates/profiles/strategist.json +51 -0
- package/templates/workspace/00-inbox/INBOX.md +7 -0
- package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
- package/templates/workspace/00-inbox/native/.gitkeep +0 -0
- package/templates/workspace/01-daily/.gitkeep +0 -0
- package/templates/workspace/02-personal/CONFLICTS.md +7 -0
- package/templates/workspace/02-personal/MEMORY.md +15 -0
- package/templates/workspace/02-personal/goals/.gitkeep +0 -0
- package/templates/workspace/02-personal/identity/.gitkeep +0 -0
- package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
- package/templates/workspace/03-professional/.gitkeep +0 -0
- package/templates/workspace/04-projects/PROJECTS.md +5 -0
- package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
- package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
- package/templates/workspace/06-templates/TEMPLATES.md +25 -0
- package/templates/workspace/06-templates/decision.md +8 -0
- package/templates/workspace/06-templates/intelligence.md +32 -0
- package/templates/workspace/06-templates/project-claude.md +48 -0
- package/templates/workspace/06-templates/session.md +38 -0
- package/templates/workspace/06-templates/task.md +42 -0
- package/templates/workspace/MANIFEST.json +51 -0
- package/templates/workspace/bridge/config/CONFIG.md +21 -0
- package/templates/workspace/bridge/config/runtime.json +4 -0
- package/templates/workspace/charter/core.md +104 -0
- package/templates/workspace/charter/policies/README.md +31 -0
- package/templates/workspace/charter/policies/claude-code-context.md +90 -0
- package/templates/workspace/charter/policies/concise-output.md +29 -0
- package/templates/workspace/charter/policies/context.md +91 -0
- package/templates/workspace/charter/policies/contracts/task.md +189 -0
- package/templates/workspace/charter/policies/git.md +60 -0
- package/templates/workspace/charter/policies/graph.md +47 -0
- package/templates/workspace/charter/policies/knowledge.md +33 -0
- package/templates/workspace/charter/policies/lifecycle.md +119 -0
- package/templates/workspace/charter/policies/memory.md +67 -0
- package/templates/workspace/charter/policies/minimal-change.md +43 -0
- package/templates/workspace/charter/policies/models.md +57 -0
- package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
- package/templates/workspace/charter/policies/response.md +39 -0
- package/templates/workspace/charter/policies/skill-observer.md +44 -0
- package/templates/workspace/charter/policies/stack.md +23 -0
- package/templates/workspace/charter/policies/task.md +139 -0
- package/templates/workspace/charter/policies/verification.md +29 -0
- package/templates/workspace/sessions/.gitkeep +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { accessSync, chmodSync, constants } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
// node-pty is optional: it has no Linux prebuild, so npm compiles it there and skips it when no
|
|
6
|
+
// compiler toolchain is present. Without it, a provider still runs, attached to this terminal
|
|
7
|
+
// directly, but its terminal output is not recorded.
|
|
8
|
+
let ptyModule;
|
|
9
|
+
const loadPty = () => (ptyModule ??= import("node-pty").catch(() => undefined));
|
|
10
|
+
// node-pty's npm tarball ships its macOS spawn-helper without the executable bit, so every pty
|
|
11
|
+
// spawn fails after a plain `npm install`. Restore the bit once, before the first spawn.
|
|
12
|
+
let spawnHelperChecked = false;
|
|
13
|
+
function ensureSpawnHelperExecutable() {
|
|
14
|
+
if (spawnHelperChecked || process.platform === "win32")
|
|
15
|
+
return;
|
|
16
|
+
spawnHelperChecked = true;
|
|
17
|
+
const root = path.resolve(path.dirname(createRequire(import.meta.url).resolve("node-pty")), "..");
|
|
18
|
+
for (const helper of [
|
|
19
|
+
path.join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
|
|
20
|
+
path.join(root, "build", "Release", "spawn-helper"),
|
|
21
|
+
]) {
|
|
22
|
+
try {
|
|
23
|
+
accessSync(helper, constants.X_OK);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
try {
|
|
27
|
+
chmodSync(helper, 0o755);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Missing (a native build provides its own) or not ours to change: the spawn reports it.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function runInteractive(request) {
|
|
36
|
+
const pty = await loadPty();
|
|
37
|
+
if (!pty) {
|
|
38
|
+
process.stderr.write("ocean: node-pty is not installed, so this session's terminal output is not recorded (see docs/troubleshooting.md)\n");
|
|
39
|
+
return await runPassthrough(request);
|
|
40
|
+
}
|
|
41
|
+
return await new Promise((resolve, reject) => {
|
|
42
|
+
let terminal;
|
|
43
|
+
try {
|
|
44
|
+
ensureSpawnHelperExecutable();
|
|
45
|
+
terminal = pty.spawn(request.command, request.args, {
|
|
46
|
+
name: process.env.TERM || "xterm-256color",
|
|
47
|
+
cols: process.stdout.columns || 120,
|
|
48
|
+
rows: process.stdout.rows || 40,
|
|
49
|
+
cwd: request.cwd,
|
|
50
|
+
env: { ...process.env, ...request.env },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
reject(error);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let output = "";
|
|
58
|
+
let timedOut = false;
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
let timer;
|
|
61
|
+
let forceKillTimer;
|
|
62
|
+
const maxCapture = 64_000;
|
|
63
|
+
const onData = (data) => {
|
|
64
|
+
if (output.length < maxCapture)
|
|
65
|
+
output += data.slice(0, maxCapture - output.length);
|
|
66
|
+
process.stdout.write(data);
|
|
67
|
+
request.onData?.(data.slice(0, 64_000));
|
|
68
|
+
};
|
|
69
|
+
terminal.onData(onData);
|
|
70
|
+
const stdinIsTTY = Boolean(process.stdin.isTTY && process.stdin.setRawMode);
|
|
71
|
+
const onStdin = (data) => {
|
|
72
|
+
const input = data.toString();
|
|
73
|
+
request.onInput?.(input);
|
|
74
|
+
terminal.write(input);
|
|
75
|
+
};
|
|
76
|
+
if (stdinIsTTY) {
|
|
77
|
+
process.stdin.setRawMode?.(true);
|
|
78
|
+
process.stdin.resume();
|
|
79
|
+
process.stdin.on("data", onStdin);
|
|
80
|
+
}
|
|
81
|
+
const onResize = () => terminal.resize(process.stdout.columns || 120, process.stdout.rows || 40);
|
|
82
|
+
process.stdout.on("resize", onResize);
|
|
83
|
+
const stop = (reason) => {
|
|
84
|
+
if (reason === "timeout")
|
|
85
|
+
timedOut = true;
|
|
86
|
+
else
|
|
87
|
+
cancelled = true;
|
|
88
|
+
terminal.kill("SIGTERM");
|
|
89
|
+
forceKillTimer = setTimeout(() => terminal.kill("SIGKILL"), 100);
|
|
90
|
+
};
|
|
91
|
+
const onAbort = () => stop("cancel");
|
|
92
|
+
if (request.timeoutMs !== undefined)
|
|
93
|
+
timer = setTimeout(() => stop("timeout"), request.timeoutMs);
|
|
94
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
95
|
+
if (request.signal?.aborted)
|
|
96
|
+
onAbort();
|
|
97
|
+
terminal.onExit(({ exitCode }) => {
|
|
98
|
+
if (timer)
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
if (forceKillTimer)
|
|
101
|
+
clearTimeout(forceKillTimer);
|
|
102
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
103
|
+
if (stdinIsTTY) {
|
|
104
|
+
process.stdin.off("data", onStdin);
|
|
105
|
+
process.stdin.setRawMode?.(false);
|
|
106
|
+
process.stdin.pause();
|
|
107
|
+
}
|
|
108
|
+
process.stdout.off("resize", onResize);
|
|
109
|
+
resolve({ exitCode, output, timedOut, cancelled });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export function runPassthrough(request) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const child = spawn(request.command, request.args, {
|
|
116
|
+
cwd: request.cwd,
|
|
117
|
+
env: { ...process.env, ...request.env },
|
|
118
|
+
stdio: "inherit",
|
|
119
|
+
});
|
|
120
|
+
const timer = request.timeoutMs === undefined
|
|
121
|
+
? undefined
|
|
122
|
+
: setTimeout(() => child.kill("SIGTERM"), request.timeoutMs);
|
|
123
|
+
const onAbort = () => {
|
|
124
|
+
child.kill("SIGTERM");
|
|
125
|
+
};
|
|
126
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
127
|
+
child.once("error", (error) => {
|
|
128
|
+
if (timer)
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
131
|
+
reject(error);
|
|
132
|
+
});
|
|
133
|
+
child.once("close", (exitCode) => {
|
|
134
|
+
if (timer)
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
137
|
+
resolve({
|
|
138
|
+
exitCode: exitCode ?? 1,
|
|
139
|
+
output: "",
|
|
140
|
+
cancelled: request.signal?.aborted,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=interactive-process.js.map
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { StringDecoder } from "node:string_decoder";
|
|
2
|
+
// The one place that decides how a headless run ends, for every provider that runs a process: the
|
|
3
|
+
// run's timeout (124), its output budget (125), the process group it runs in, the signals passed
|
|
4
|
+
// on to it, and how stdout becomes lines. `runHeadless` (CLI providers) and the Claude Agent SDK
|
|
5
|
+
// runner both use it, so a fix to any of these lands once.
|
|
6
|
+
// A process run in its own group is stopped as a whole, so a timeout or budget stop ends
|
|
7
|
+
// everything it started (a tool's subprocess too), not just the leader: a surviving descendant
|
|
8
|
+
// would hold the output pipe open and keep the run waiting.
|
|
9
|
+
export const ownsGroup = process.platform !== "win32";
|
|
10
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
11
|
+
// How long output still in the pipe gets, once the process itself has exited, before whatever
|
|
12
|
+
// else holds the pipe open (a background process it left behind) is stopped and no longer awaited.
|
|
13
|
+
const LINGER_MS = 500;
|
|
14
|
+
export function signalTree(child, signal) {
|
|
15
|
+
if (!child?.pid)
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
if (ownsGroup)
|
|
19
|
+
process.kill(-child.pid, signal);
|
|
20
|
+
else
|
|
21
|
+
child.kill(signal);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Already gone.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Out of the terminal's process group, a run no longer receives this process's Ctrl+C or kill
|
|
28
|
+
// directly; pass them on, then let the signal do what it would have done here. One set of
|
|
29
|
+
// listeners serves every run in flight, so many concurrent runs never trip Node's listener warning.
|
|
30
|
+
const active = new Set();
|
|
31
|
+
let listening = false;
|
|
32
|
+
const onExit = () => {
|
|
33
|
+
for (const child of active)
|
|
34
|
+
signalTree(child, "SIGTERM");
|
|
35
|
+
};
|
|
36
|
+
const onSignal = (signal) => {
|
|
37
|
+
onExit();
|
|
38
|
+
unlisten();
|
|
39
|
+
if (process.listenerCount(signal) === 0)
|
|
40
|
+
process.kill(process.pid, signal);
|
|
41
|
+
};
|
|
42
|
+
function unlisten() {
|
|
43
|
+
if (!listening)
|
|
44
|
+
return;
|
|
45
|
+
listening = false;
|
|
46
|
+
process.off("exit", onExit);
|
|
47
|
+
for (const signal of forwardedSignals)
|
|
48
|
+
process.off(signal, onSignal);
|
|
49
|
+
}
|
|
50
|
+
function endWithThisProcess(child) {
|
|
51
|
+
if (!listening) {
|
|
52
|
+
listening = true;
|
|
53
|
+
process.once("exit", onExit);
|
|
54
|
+
for (const signal of forwardedSignals)
|
|
55
|
+
process.on(signal, onSignal);
|
|
56
|
+
}
|
|
57
|
+
active.add(child);
|
|
58
|
+
return () => {
|
|
59
|
+
active.delete(child);
|
|
60
|
+
if (active.size === 0)
|
|
61
|
+
unlisten();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function superviseProcess(options) {
|
|
65
|
+
let child;
|
|
66
|
+
let release = () => { };
|
|
67
|
+
let spawnError;
|
|
68
|
+
let stderr = "";
|
|
69
|
+
let outputBytes = 0;
|
|
70
|
+
let timedOut = false;
|
|
71
|
+
let outputLimitExceeded = false;
|
|
72
|
+
let terminating = false;
|
|
73
|
+
let killTimer;
|
|
74
|
+
let lingerTimer;
|
|
75
|
+
let letGoNow = () => { };
|
|
76
|
+
const released = new Promise((resolve) => {
|
|
77
|
+
letGoNow = resolve;
|
|
78
|
+
});
|
|
79
|
+
let closedNow = () => { };
|
|
80
|
+
const closed = new Promise((resolve) => {
|
|
81
|
+
closedNow = resolve;
|
|
82
|
+
});
|
|
83
|
+
// Whatever still holds the pipes (a descendant that left the group) no longer holds the run.
|
|
84
|
+
const letGo = () => {
|
|
85
|
+
child?.stdout?.destroy();
|
|
86
|
+
child?.stderr?.destroy();
|
|
87
|
+
letGoNow();
|
|
88
|
+
};
|
|
89
|
+
const terminate = () => {
|
|
90
|
+
if (terminating)
|
|
91
|
+
return;
|
|
92
|
+
terminating = true;
|
|
93
|
+
options.onTerminate?.();
|
|
94
|
+
signalTree(child, "SIGTERM");
|
|
95
|
+
// Not unref'd: this timer keeps the process alive until the run has been let go.
|
|
96
|
+
killTimer = setTimeout(() => {
|
|
97
|
+
signalTree(child, "SIGKILL");
|
|
98
|
+
letGo();
|
|
99
|
+
}, 100);
|
|
100
|
+
};
|
|
101
|
+
// Armed until the process has closed, not just until the output stops.
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
timedOut = true;
|
|
104
|
+
terminate();
|
|
105
|
+
}, options.timeoutMs);
|
|
106
|
+
// Raw stdout and stderr bytes count against one budget as they arrive, before anything buffers
|
|
107
|
+
// a line, so a huge or unterminated line cannot grow past the cap.
|
|
108
|
+
const withinBudget = (bytes) => {
|
|
109
|
+
if (outputLimitExceeded)
|
|
110
|
+
return false;
|
|
111
|
+
if (outputBytes + bytes > options.maxOutputBytes) {
|
|
112
|
+
outputLimitExceeded = true;
|
|
113
|
+
terminate();
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
outputBytes += bytes;
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
const decoder = new StringDecoder("utf8");
|
|
120
|
+
let pending = "";
|
|
121
|
+
const emit = (line) => {
|
|
122
|
+
if (line)
|
|
123
|
+
options.onLine?.(line);
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
attach(spawned) {
|
|
127
|
+
child = spawned;
|
|
128
|
+
if (ownsGroup)
|
|
129
|
+
release = endWithThisProcess(spawned);
|
|
130
|
+
// A failed start (no such executable) is a failed run; the abort of a stop we asked for is not.
|
|
131
|
+
spawned.once("error", (error) => {
|
|
132
|
+
if (terminating)
|
|
133
|
+
return;
|
|
134
|
+
spawnError = error;
|
|
135
|
+
letGo();
|
|
136
|
+
});
|
|
137
|
+
spawned.once("exit", () => {
|
|
138
|
+
if (!terminating)
|
|
139
|
+
lingerTimer = setTimeout(letGo, LINGER_MS);
|
|
140
|
+
});
|
|
141
|
+
spawned.once("close", closedNow);
|
|
142
|
+
spawned.stdout?.on("data", (chunk) => {
|
|
143
|
+
if (!withinBudget(chunk.byteLength))
|
|
144
|
+
return;
|
|
145
|
+
pending += decoder.write(chunk);
|
|
146
|
+
const lines = pending.split(/\r?\n/);
|
|
147
|
+
pending = lines.pop() ?? "";
|
|
148
|
+
for (const line of lines)
|
|
149
|
+
emit(line);
|
|
150
|
+
});
|
|
151
|
+
spawned.stderr?.on("data", (chunk) => {
|
|
152
|
+
if (withinBudget(chunk.byteLength))
|
|
153
|
+
stderr += chunk.toString("utf8");
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
closed,
|
|
157
|
+
released,
|
|
158
|
+
get spawnError() {
|
|
159
|
+
return spawnError;
|
|
160
|
+
},
|
|
161
|
+
get timedOut() {
|
|
162
|
+
return timedOut;
|
|
163
|
+
},
|
|
164
|
+
get outputLimitExceeded() {
|
|
165
|
+
return outputLimitExceeded;
|
|
166
|
+
},
|
|
167
|
+
get stderr() {
|
|
168
|
+
return stderr;
|
|
169
|
+
},
|
|
170
|
+
get stopNote() {
|
|
171
|
+
return timedOut
|
|
172
|
+
? "Process timed out.\n"
|
|
173
|
+
: outputLimitExceeded
|
|
174
|
+
? "Output budget exceeded.\n"
|
|
175
|
+
: "";
|
|
176
|
+
},
|
|
177
|
+
exitCode(code) {
|
|
178
|
+
return timedOut ? 124 : outputLimitExceeded ? 125 : (code ?? 1);
|
|
179
|
+
},
|
|
180
|
+
flush() {
|
|
181
|
+
emit(pending + decoder.end());
|
|
182
|
+
pending = "";
|
|
183
|
+
},
|
|
184
|
+
finish() {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
clearTimeout(lingerTimer);
|
|
187
|
+
clearTimeout(killTimer);
|
|
188
|
+
// The leader is gone; end anything it left in the group, then stop watching it.
|
|
189
|
+
signalTree(child, "SIGKILL");
|
|
190
|
+
release();
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=process-supervisor.js.map
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { reindexBrain } from "@ocean/brain/brain/brain-reindex.js";
|
|
7
|
+
import { brainIndexState } from "@ocean/brain/brain/brain-service.js";
|
|
8
|
+
import { brainIndexPort } from "@ocean/brain/infrastructure/brain/brain-index.js";
|
|
9
|
+
import { loadObsidianConnection } from "@ocean/services/obsidian/vault-discovery.js";
|
|
10
|
+
import { watchObsidianVault } from "@ocean/services/obsidian/vault-sync.js";
|
|
11
|
+
import { refreshClaudeProjects } from "../../application/pull/server-capture.js";
|
|
12
|
+
import { createOllamaEmbedder } from "../providers/ollama-embedder.js";
|
|
13
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
14
|
+
const REINDEX_CHECK_MS = 2 * 60 * 1000;
|
|
15
|
+
const CLAUDE_PROJECTS_MS = 15 * 60 * 1000;
|
|
16
|
+
let reindexing = false;
|
|
17
|
+
// Keeps the brain index current (T-307): a record written over MCP, an approval or a hand edit is
|
|
18
|
+
// searchable within minutes, without `ocean memory reindex`. The build goes to a temp file renamed
|
|
19
|
+
// into place, so a search never reads half an index. Vectors when the local embedder answers;
|
|
20
|
+
// without one, an index that has vectors waits for it rather than losing them, and one without is
|
|
21
|
+
// rebuilt as full text. OCEAN_NO_AUTO_REINDEX=1 turns it off.
|
|
22
|
+
export async function reindexWhenStale(options = {}) {
|
|
23
|
+
if (process.env.OCEAN_NO_AUTO_REINDEX === "1")
|
|
24
|
+
return "off";
|
|
25
|
+
if (reindexing)
|
|
26
|
+
return "busy";
|
|
27
|
+
reindexing = true;
|
|
28
|
+
try {
|
|
29
|
+
const state = await brainIndexState({
|
|
30
|
+
root: options.root,
|
|
31
|
+
indexPort: brainIndexPort,
|
|
32
|
+
});
|
|
33
|
+
if (!state.stale)
|
|
34
|
+
return "fresh";
|
|
35
|
+
const embedder = await (options.embedder ?? (() => createOllamaEmbedder()))().catch(() => null);
|
|
36
|
+
if (!embedder && state.hasVectors)
|
|
37
|
+
return "waiting";
|
|
38
|
+
await reindexBrain({
|
|
39
|
+
root: options.root,
|
|
40
|
+
embedder,
|
|
41
|
+
indexPort: brainIndexPort,
|
|
42
|
+
});
|
|
43
|
+
return "rebuilt";
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
reindexing = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// The owner's Claude project names, so a conversation in a new Claude project can register it
|
|
50
|
+
// (T-307). Reading the caches takes tens of seconds, so it runs here, never in a tool call.
|
|
51
|
+
function startClaudeProjectsRefresh() {
|
|
52
|
+
if (process.env.OCEAN_NO_PULL_ON_START === "1")
|
|
53
|
+
return;
|
|
54
|
+
refreshClaudeProjects(os.homedir()).catch((error) => {
|
|
55
|
+
console.error(`Claude project names not refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function startAutoReindex() {
|
|
59
|
+
reindexWhenStale().catch((error) => {
|
|
60
|
+
console.error(`brain reindex failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// The daily `ocean pull --yes` (T-305) in a detached child; it returns at once when the last pull
|
|
64
|
+
// is under a day old. Session starts run it, and so does the always-on service, so a user who only
|
|
65
|
+
// opens Claude Desktop is pulled too. OCEAN_NO_PULL_ON_START=1 turns both off.
|
|
66
|
+
export function startBackgroundPull() {
|
|
67
|
+
if (process.env.OCEAN_NO_PULL_ON_START === "1")
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
spawn(process.execPath, [process.argv[1], "pull", "--yes", "--daily"], {
|
|
71
|
+
detached: true,
|
|
72
|
+
stdio: "ignore",
|
|
73
|
+
})
|
|
74
|
+
.on("error", () => { })
|
|
75
|
+
.unref();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// no pull this time; the next try runs it
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const runCommand = (command, args) => promisify(execFile)(command, args);
|
|
82
|
+
function serviceContext(options) {
|
|
83
|
+
const home = options.home ?? os.homedir();
|
|
84
|
+
const domain = `gui/${process.getuid?.() ?? ""}`;
|
|
85
|
+
return {
|
|
86
|
+
home,
|
|
87
|
+
platform: options.platform ?? process.platform,
|
|
88
|
+
run: options.run ?? runCommand,
|
|
89
|
+
wait: options.wait ??
|
|
90
|
+
((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
91
|
+
plist: path.join(home, "Library", "LaunchAgents", "com.ocean.runtime.plist"),
|
|
92
|
+
unit: path.join(home, ".config", "systemd", "user", "ocean.service"),
|
|
93
|
+
domain,
|
|
94
|
+
label: `${domain}/com.ocean.runtime`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const loaded = (context) => context.run("launchctl", ["print", context.label]).then(() => true, () => false);
|
|
98
|
+
// Loads the launchd job: three tries a second apart, then the error names the command that
|
|
99
|
+
// starts it, since nothing else would retry it.
|
|
100
|
+
async function bootstrap(context) {
|
|
101
|
+
let failure;
|
|
102
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
103
|
+
if (attempt)
|
|
104
|
+
await context.wait(1000);
|
|
105
|
+
try {
|
|
106
|
+
await context.run("launchctl", [
|
|
107
|
+
"bootstrap",
|
|
108
|
+
context.domain,
|
|
109
|
+
context.plist,
|
|
110
|
+
]);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
failure = error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`${failure instanceof Error ? failure.message : String(failure)} — the service is stopped; start it with: launchctl bootstrap ${context.domain} ${context.plist}`);
|
|
118
|
+
}
|
|
119
|
+
// A running service keeps the engine it started with, whatever `current` or its start file now
|
|
120
|
+
// say (T-305), so a new build or a repointed start file restarts it. With the start file
|
|
121
|
+
// unchanged (`reload: false`), launchd's kickstart restarts the process while the job stays
|
|
122
|
+
// loaded, so KeepAlive covers it throughout (T-307). A changed file needs the job unloaded and
|
|
123
|
+
// loaded again: bootout returns before launchd has removed the job, and a bootstrap while it is
|
|
124
|
+
// still there fails ("Bootstrap failed: 5: Input/output error"), so it waits until `print` no
|
|
125
|
+
// longer finds it (up to 5 s) and retries. systemd re-reads the unit on reload; the Windows
|
|
126
|
+
// startup script has no manager and takes effect at next login. Never throws: it runs inside
|
|
127
|
+
// session starts, which must not fail on it.
|
|
128
|
+
export async function restartService(options = {}) {
|
|
129
|
+
const context = serviceContext(options);
|
|
130
|
+
const { run, wait } = context;
|
|
131
|
+
try {
|
|
132
|
+
if (context.platform === "darwin") {
|
|
133
|
+
if (!existsSync(context.plist))
|
|
134
|
+
return "not-installed";
|
|
135
|
+
if (options.reload === false) {
|
|
136
|
+
const kicked = await run("launchctl", [
|
|
137
|
+
"kickstart",
|
|
138
|
+
"-k",
|
|
139
|
+
context.label,
|
|
140
|
+
]).then(() => true, () => false);
|
|
141
|
+
if (!kicked)
|
|
142
|
+
await bootstrap(context);
|
|
143
|
+
return "restarted";
|
|
144
|
+
}
|
|
145
|
+
await run("launchctl", ["bootout", context.label]).catch(() => { });
|
|
146
|
+
for (let poll = 0; poll < 20 && (await loaded(context)); poll += 1)
|
|
147
|
+
await wait(250);
|
|
148
|
+
await bootstrap(context);
|
|
149
|
+
return "restarted";
|
|
150
|
+
}
|
|
151
|
+
if (context.platform === "linux") {
|
|
152
|
+
if (!existsSync(context.unit))
|
|
153
|
+
return "not-installed";
|
|
154
|
+
await run("systemctl", ["--user", "daemon-reload"]);
|
|
155
|
+
await run("systemctl", ["--user", "try-restart", "ocean.service"]);
|
|
156
|
+
return "restarted";
|
|
157
|
+
}
|
|
158
|
+
const startup = path.join(process.env.APPDATA ?? path.join(context.home, "AppData", "Roaming"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "ocean.cmd");
|
|
159
|
+
return context.platform === "win32" && existsSync(startup)
|
|
160
|
+
? "next-login"
|
|
161
|
+
: "not-installed";
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return { failed: error instanceof Error ? error.message : String(error) };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// The safety net (T-307): every session start makes sure an installed service is loaded and
|
|
168
|
+
// running, so nothing — a failed restart, a manual bootout — leaves it stopped past the next
|
|
169
|
+
// session. Windows has no service manager to ask. Never throws.
|
|
170
|
+
export async function ensureService(options = {}) {
|
|
171
|
+
const context = serviceContext(options);
|
|
172
|
+
try {
|
|
173
|
+
if (context.platform === "darwin") {
|
|
174
|
+
if (!existsSync(context.plist))
|
|
175
|
+
return "not-installed";
|
|
176
|
+
if (await loaded(context))
|
|
177
|
+
return "running";
|
|
178
|
+
await bootstrap(context);
|
|
179
|
+
return "started";
|
|
180
|
+
}
|
|
181
|
+
if (context.platform === "linux") {
|
|
182
|
+
if (!existsSync(context.unit))
|
|
183
|
+
return "not-installed";
|
|
184
|
+
const active = await context
|
|
185
|
+
.run("systemctl", ["--user", "is-active", "--quiet", "ocean.service"])
|
|
186
|
+
.then(() => true, () => false);
|
|
187
|
+
if (active)
|
|
188
|
+
return "running";
|
|
189
|
+
await context.run("systemctl", ["--user", "start", "ocean.service"]);
|
|
190
|
+
return "started";
|
|
191
|
+
}
|
|
192
|
+
return "unchecked";
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return { failed: error instanceof Error ? error.message : String(error) };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export async function runService(shutdownAfterMs) {
|
|
199
|
+
const controller = new AbortController();
|
|
200
|
+
let watcher;
|
|
201
|
+
try {
|
|
202
|
+
const connection = await loadObsidianConnection();
|
|
203
|
+
watcher = watchObsidianVault(connection, undefined, controller.signal).catch((error) => {
|
|
204
|
+
console.error(`Obsidian watcher stopped: ${error instanceof Error ? error.message : String(error)}`);
|
|
205
|
+
});
|
|
206
|
+
console.log(`Obsidian watcher enabled: ${connection.vaultPath}`);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error.code !== "ENOENT") {
|
|
210
|
+
console.error(`Obsidian watcher unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const heartbeat = setInterval(() => undefined, 60_000);
|
|
214
|
+
startBackgroundPull();
|
|
215
|
+
const dailyPull = setInterval(startBackgroundPull, HOUR_MS);
|
|
216
|
+
startAutoReindex();
|
|
217
|
+
const autoReindex = setInterval(startAutoReindex, REINDEX_CHECK_MS);
|
|
218
|
+
startClaudeProjectsRefresh();
|
|
219
|
+
const claudeProjects = setInterval(startClaudeProjectsRefresh, CLAUDE_PROJECTS_MS);
|
|
220
|
+
await new Promise((resolve) => {
|
|
221
|
+
let testShutdown;
|
|
222
|
+
const shutdown = (signal) => {
|
|
223
|
+
console.log(`Ocean runtime received ${signal}, shutting down.`);
|
|
224
|
+
clearInterval(heartbeat);
|
|
225
|
+
clearInterval(dailyPull);
|
|
226
|
+
clearInterval(autoReindex);
|
|
227
|
+
clearInterval(claudeProjects);
|
|
228
|
+
controller.abort();
|
|
229
|
+
process.off("SIGINT", shutdown);
|
|
230
|
+
process.off("SIGTERM", shutdown);
|
|
231
|
+
process.off("message", onMessage);
|
|
232
|
+
if (testShutdown)
|
|
233
|
+
clearTimeout(testShutdown);
|
|
234
|
+
resolve();
|
|
235
|
+
};
|
|
236
|
+
const onMessage = (message) => {
|
|
237
|
+
if (message === "shutdown")
|
|
238
|
+
shutdown("IPC");
|
|
239
|
+
};
|
|
240
|
+
process.on("SIGINT", shutdown);
|
|
241
|
+
process.on("SIGTERM", shutdown);
|
|
242
|
+
process.on("message", onMessage);
|
|
243
|
+
// Only now is a SIGTERM handled gracefully; announcing earlier lets a signal that
|
|
244
|
+
// arrives during startup kill the process before it can shut down cleanly.
|
|
245
|
+
console.log("Ocean runtime is running.");
|
|
246
|
+
if (shutdownAfterMs !== undefined)
|
|
247
|
+
testShutdown = setTimeout(() => shutdown("timer"), shutdownAfterMs);
|
|
248
|
+
});
|
|
249
|
+
await watcher;
|
|
250
|
+
console.log("Ocean runtime stopped.");
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=service.js.map
|