@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,218 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defaultSkillNames } from "../skills/default-skills.js";
|
|
4
|
+
const cliClients = [
|
|
5
|
+
"claude",
|
|
6
|
+
"codex",
|
|
7
|
+
"gemini",
|
|
8
|
+
"antigravity",
|
|
9
|
+
"hermes",
|
|
10
|
+
"kilo",
|
|
11
|
+
"kimi",
|
|
12
|
+
];
|
|
13
|
+
// Completion clients reached over HTTP at a fixed endpoint (T-245 Phase 4). They return text;
|
|
14
|
+
// they never spawn a process or touch the workspace.
|
|
15
|
+
const apiClients = ["deepseek", "openrouter"];
|
|
16
|
+
const providerSchema = z.enum([...cliClients, ...apiClients]);
|
|
17
|
+
export function isApiClient(client) {
|
|
18
|
+
return apiClients.includes(client);
|
|
19
|
+
}
|
|
20
|
+
// A model is passed to a CLI as the argument after its model flag; a leading "-" could be
|
|
21
|
+
// read as another flag.
|
|
22
|
+
const modelSchema = z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.refine((model) => !model.startsWith("-"), "A model cannot start with -");
|
|
26
|
+
export const transportSchema = z.enum(["cli", "openai-compatible-api"]);
|
|
27
|
+
// `transport` and `temperature` stay optional (no default) so a v1 binding parses to exactly
|
|
28
|
+
// the object it parsed to before schema v2, and its profile identity is unchanged.
|
|
29
|
+
const clientBindingSchema = z.object({
|
|
30
|
+
enabled: z.boolean().default(true),
|
|
31
|
+
transport: transportSchema.optional(),
|
|
32
|
+
model: modelSchema.optional(),
|
|
33
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
34
|
+
// A completion client that may call Ocean's bounded tools (T-245 Phase 6). Off unless set.
|
|
35
|
+
toolLoop: z.boolean().optional(),
|
|
36
|
+
profile: z.string().min(1).optional(),
|
|
37
|
+
home: z.string().min(1).optional(),
|
|
38
|
+
mode: z.string().min(1).optional(),
|
|
39
|
+
capabilities: z.array(z.string()).default([]),
|
|
40
|
+
limitations: z.array(z.string()).default([]),
|
|
41
|
+
});
|
|
42
|
+
const governanceSchema = z.object({
|
|
43
|
+
allowedPaths: z.array(z.string()).optional(),
|
|
44
|
+
allowedCommands: z.array(z.string()).optional(),
|
|
45
|
+
writePolicy: z.enum(["none", "workspace", "allowed-paths"]).optional(),
|
|
46
|
+
approvalRequired: z.boolean().optional(),
|
|
47
|
+
network: z.string().min(1).optional(),
|
|
48
|
+
});
|
|
49
|
+
// A profile name is also a filename (profiles/<name>.json) and a memory namespace
|
|
50
|
+
// (memory/profiles/<name>.json), so it is restricted to characters that can never form a path.
|
|
51
|
+
// Lowercase only: on a case-insensitive filesystem "Alpha" and "alpha" would share one file.
|
|
52
|
+
const profileNamePattern = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
53
|
+
export function assertProfileName(name) {
|
|
54
|
+
// typeof first: RegExp#test coerces, so null would otherwise pass as "null".
|
|
55
|
+
if (typeof name !== "string" || !profileNamePattern.test(name))
|
|
56
|
+
throw new Error("Invalid profile name");
|
|
57
|
+
return name;
|
|
58
|
+
}
|
|
59
|
+
const rawProfileSchema = z.object({
|
|
60
|
+
// Absent means 1. Schema v1 stays readable for one release; v2 is what new profiles use.
|
|
61
|
+
schemaVersion: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
62
|
+
name: z.string().regex(profileNamePattern, "Invalid profile name"),
|
|
63
|
+
description: z.string().default(""),
|
|
64
|
+
version: z.string().min(1).default("1.0.0"),
|
|
65
|
+
provider: providerSchema.optional(),
|
|
66
|
+
model: modelSchema.optional(),
|
|
67
|
+
role: z.string().min(1),
|
|
68
|
+
skills: z.array(z.string()).default([]),
|
|
69
|
+
allowedPaths: z.array(z.string()).default([]),
|
|
70
|
+
allowedCommands: z.array(z.string()).default([]),
|
|
71
|
+
writePolicy: z.enum(["none", "workspace", "allowed-paths"]).default("none"),
|
|
72
|
+
contextSources: z.array(z.string()).default([]),
|
|
73
|
+
clients: z.record(z.string(), clientBindingSchema).default({}),
|
|
74
|
+
defaultClient: providerSchema.optional(),
|
|
75
|
+
governance: governanceSchema.optional(),
|
|
76
|
+
memory: z
|
|
77
|
+
.object({
|
|
78
|
+
enabled: z.boolean().default(true),
|
|
79
|
+
scope: z.string().min(1).default("profile"),
|
|
80
|
+
})
|
|
81
|
+
.default({ enabled: true, scope: "profile" }),
|
|
82
|
+
verification: z
|
|
83
|
+
.object({ commands: z.array(z.string()).default([]) })
|
|
84
|
+
.default({ commands: [] }),
|
|
85
|
+
contextCompression: z.enum(["none", "ocean-bounded"]).default("none"),
|
|
86
|
+
instructions: z.string().default(""),
|
|
87
|
+
});
|
|
88
|
+
// Schema v2 makes explicit what v1 left to defaults: a default client, a model on every
|
|
89
|
+
// enabled client, and a closed memory scope.
|
|
90
|
+
function refineV2(input, context) {
|
|
91
|
+
const issue = (path, message) => context.addIssue({ code: z.ZodIssueCode.custom, path, message });
|
|
92
|
+
if (!input.defaultClient)
|
|
93
|
+
issue(["defaultClient"], "Schema v2 requires a defaultClient");
|
|
94
|
+
else if (!input.clients[input.defaultClient]?.enabled)
|
|
95
|
+
issue(["defaultClient"], "defaultClient must be an enabled client");
|
|
96
|
+
for (const [client, binding] of Object.entries(input.clients))
|
|
97
|
+
if (binding.enabled && !binding.model)
|
|
98
|
+
issue(["clients", client, "model"], "Schema v2 requires a model");
|
|
99
|
+
if (!["profile", "disabled"].includes(input.memory.scope))
|
|
100
|
+
issue(["memory", "scope"], 'Schema v2 memory.scope is "profile" or "disabled"');
|
|
101
|
+
}
|
|
102
|
+
export const profileSchema = rawProfileSchema
|
|
103
|
+
.superRefine((input, context) => {
|
|
104
|
+
if (input.schemaVersion === 2)
|
|
105
|
+
refineV2(input, context);
|
|
106
|
+
for (const [client, binding] of Object.entries(input.clients)) {
|
|
107
|
+
if (!providerSchema.safeParse(client).success)
|
|
108
|
+
context.addIssue({
|
|
109
|
+
code: z.ZodIssueCode.custom,
|
|
110
|
+
path: ["clients", client],
|
|
111
|
+
message: `Unsupported client: ${client}`,
|
|
112
|
+
});
|
|
113
|
+
// A client's transport is fixed by what it is: a CLI cannot be called over HTTP and a
|
|
114
|
+
// completion API cannot be spawned. An API client has no provider default model.
|
|
115
|
+
const expected = isApiClient(client) ? "openai-compatible-api" : "cli";
|
|
116
|
+
if (binding.transport && binding.transport !== expected)
|
|
117
|
+
context.addIssue({
|
|
118
|
+
code: z.ZodIssueCode.custom,
|
|
119
|
+
path: ["clients", client, "transport"],
|
|
120
|
+
message: `Client ${client} uses transport ${expected}`,
|
|
121
|
+
});
|
|
122
|
+
if (binding.toolLoop && !isApiClient(client))
|
|
123
|
+
context.addIssue({
|
|
124
|
+
code: z.ZodIssueCode.custom,
|
|
125
|
+
path: ["clients", client, "toolLoop"],
|
|
126
|
+
message: `Client ${client} runs its own tools; toolLoop is for completion clients`,
|
|
127
|
+
});
|
|
128
|
+
if (isApiClient(client) &&
|
|
129
|
+
binding.enabled &&
|
|
130
|
+
(!binding.model || binding.model === "provider-managed"))
|
|
131
|
+
context.addIssue({
|
|
132
|
+
code: z.ZodIssueCode.custom,
|
|
133
|
+
path: ["clients", client, "model"],
|
|
134
|
+
message: `Client ${client} requires a model`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (!input.provider &&
|
|
138
|
+
!input.defaultClient &&
|
|
139
|
+
!Object.entries(input.clients).some(([, binding]) => binding.enabled)) {
|
|
140
|
+
context.addIssue({
|
|
141
|
+
code: z.ZodIssueCode.custom,
|
|
142
|
+
path: ["clients"],
|
|
143
|
+
message: "Profile must select a provider or an enabled client",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.transform((input) => {
|
|
148
|
+
const provider = input.provider ??
|
|
149
|
+
input.defaultClient ??
|
|
150
|
+
Object.entries(input.clients).find(([, binding]) => binding.enabled)?.[0];
|
|
151
|
+
const model = input.model ?? input.clients[provider]?.model ?? "provider-managed";
|
|
152
|
+
const governance = input.governance ?? {};
|
|
153
|
+
return {
|
|
154
|
+
...input,
|
|
155
|
+
skills: input.skills.length
|
|
156
|
+
? input.skills
|
|
157
|
+
: defaultSkillNames(input.role),
|
|
158
|
+
provider,
|
|
159
|
+
model,
|
|
160
|
+
defaultClient: input.defaultClient ?? provider,
|
|
161
|
+
allowedPaths: governance.allowedPaths ?? input.allowedPaths,
|
|
162
|
+
allowedCommands: governance.allowedCommands ?? input.allowedCommands,
|
|
163
|
+
writePolicy: governance.writePolicy ?? input.writePolicy,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
export function selectProfileClient(profile, requested) {
|
|
167
|
+
const value = requested ?? profile.defaultClient ?? profile.provider;
|
|
168
|
+
const parsed = providerSchema.safeParse(value);
|
|
169
|
+
if (!parsed.success)
|
|
170
|
+
throw new Error(`Unsupported client: ${value}`);
|
|
171
|
+
const client = parsed.data;
|
|
172
|
+
const binding = profile.clients[client];
|
|
173
|
+
if (Object.keys(profile.clients).length && !binding?.enabled)
|
|
174
|
+
throw new Error(`Client is not enabled for profile: ${client}`);
|
|
175
|
+
// The profile-level model belongs to the profile's own provider. Another client without a
|
|
176
|
+
// model of its own keeps its provider default: since the model became a CLI flag, passing
|
|
177
|
+
// e.g. a Claude model to codex would break the run.
|
|
178
|
+
return {
|
|
179
|
+
...profile,
|
|
180
|
+
provider: client,
|
|
181
|
+
defaultClient: client,
|
|
182
|
+
model: binding?.model ??
|
|
183
|
+
(client === profile.provider ? profile.model : "provider-managed"),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// A deterministic identity for the exact configuration a profile had at the moment a
|
|
187
|
+
// session was created from it: same fields (including `version`) always hash to the same
|
|
188
|
+
// value, so a session can be traced back to the profile shape that produced it even after
|
|
189
|
+
// the profile file on disk is later edited. Key order is fixed explicitly rather than
|
|
190
|
+
// relying on object insertion order, since callers may construct a Profile in any order.
|
|
191
|
+
export function profileIdentity(profile) {
|
|
192
|
+
const canonical = {
|
|
193
|
+
name: profile.name,
|
|
194
|
+
description: profile.description,
|
|
195
|
+
version: profile.version,
|
|
196
|
+
provider: profile.provider,
|
|
197
|
+
model: profile.model,
|
|
198
|
+
role: profile.role,
|
|
199
|
+
skills: profile.skills,
|
|
200
|
+
allowedPaths: profile.allowedPaths,
|
|
201
|
+
allowedCommands: profile.allowedCommands,
|
|
202
|
+
writePolicy: profile.writePolicy,
|
|
203
|
+
contextSources: profile.contextSources,
|
|
204
|
+
clients: profile.clients,
|
|
205
|
+
defaultClient: profile.defaultClient,
|
|
206
|
+
memory: profile.memory,
|
|
207
|
+
verification: profile.verification,
|
|
208
|
+
contextCompression: profile.contextCompression,
|
|
209
|
+
instructions: profile.instructions,
|
|
210
|
+
// v2-only fields are appended only for v2, so every v1 identity stays what it was and a
|
|
211
|
+
// v1 session can still be resumed after this release.
|
|
212
|
+
...(profile.schemaVersion === 2
|
|
213
|
+
? { schemaVersion: profile.schemaVersion }
|
|
214
|
+
: {}),
|
|
215
|
+
};
|
|
216
|
+
return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { assertProfileName } from "./profile.js";
|
|
3
|
+
// bridge/config/runtime.json (T-245 Phase 5). Paths are derived from the Ocean root, never
|
|
4
|
+
// configured: the settings are which profile runs when none is named, and whether a run for a
|
|
5
|
+
// task may use the suggested profile instead (T-296, off unless set).
|
|
6
|
+
const runtimeConfigSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
schemaVersion: z.literal(1),
|
|
9
|
+
defaultProfile: z.string().refine((name) => {
|
|
10
|
+
try {
|
|
11
|
+
assertProfileName(name);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}, "Invalid profile name"),
|
|
18
|
+
autoProfile: z.boolean().optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
export function validateRuntimeConfig(input) {
|
|
22
|
+
return runtimeConfigSchema.parse(input);
|
|
23
|
+
}
|
|
24
|
+
// Until runtime.json exists the historical default applies, so an install that predates the
|
|
25
|
+
// file keeps working. Once it exists it is the only source; an explicit --profile wins.
|
|
26
|
+
export const LEGACY_DEFAULT_PROFILE = "default";
|
|
27
|
+
export function resolveProfileName(explicit, config) {
|
|
28
|
+
return explicit ?? config?.defaultProfile ?? LEGACY_DEFAULT_PROFILE;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=runtime-config.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const providerCapabilitySchema = z.object({
|
|
3
|
+
provider: z.enum([
|
|
4
|
+
"claude",
|
|
5
|
+
"codex",
|
|
6
|
+
"gemini",
|
|
7
|
+
"antigravity",
|
|
8
|
+
"hermes",
|
|
9
|
+
"kilo",
|
|
10
|
+
"kimi",
|
|
11
|
+
]),
|
|
12
|
+
command: z.string().min(1),
|
|
13
|
+
installed: z.boolean(),
|
|
14
|
+
headless: z.boolean(),
|
|
15
|
+
resume: z.boolean(),
|
|
16
|
+
streaming: z.boolean(),
|
|
17
|
+
structuredOutput: z.boolean(),
|
|
18
|
+
authentication: z.enum(["cli-managed", "unknown"]),
|
|
19
|
+
interactive: z.boolean().default(true),
|
|
20
|
+
interactiveContext: z
|
|
21
|
+
.enum(["verified", "partial", "not-proven"])
|
|
22
|
+
.default("not-proven"),
|
|
23
|
+
contextTransport: z.string().min(1).default("provider-specific"),
|
|
24
|
+
inputCapture: z
|
|
25
|
+
.enum(["semantic", "bounded-terminal", "none"])
|
|
26
|
+
.default("bounded-terminal"),
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=provider-capability.js.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
// Pure secret/PII redaction. Lives in the domain layer because it is a set of
|
|
3
|
+
// string transforms with no I/O — application code redacts through here instead
|
|
4
|
+
// of importing the infrastructure logger.
|
|
5
|
+
const MAX_PAYLOAD = 64_000;
|
|
6
|
+
// Credential shapes: vendor prefixes, bearer tokens, JWTs, private keys, credentialed URLs.
|
|
7
|
+
// Every pattern stays linear on hostile input (bounded runs, anchored starts): these scan
|
|
8
|
+
// file contents a model asked for, and a backtracking pattern would hang the run.
|
|
9
|
+
const shapePatterns = [
|
|
10
|
+
// \b: a prefix inside a word ("task-observer") is not a key.
|
|
11
|
+
/\b(?:sk-(?:ant-)?|AIza|ghp_|github_pat_|xox[baprs]-)[A-Za-z0-9_-]{8,}/gi,
|
|
12
|
+
// AWS access key ids, Stripe, npm, GitLab and the other GitHub token prefixes.
|
|
13
|
+
/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
14
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
15
|
+
/\bnpm_[A-Za-z0-9]{36}\b/g,
|
|
16
|
+
/\bglpat-[A-Za-z0-9_-]{20,}/g,
|
|
17
|
+
/\bgh[ousr]_[A-Za-z0-9]{36,}/g,
|
|
18
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/gi,
|
|
19
|
+
/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
20
|
+
/-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----[\s\S]{0,16384}?-----END (?:RSA |OPENSSH |EC )?PRIVATE KEY-----/g,
|
|
21
|
+
// Connection strings and webhook URLs that embed a credential in the userinfo or path
|
|
22
|
+
// segment, e.g. a database connection string or a Slack incoming-webhook URL.
|
|
23
|
+
/\b[a-zA-Z][a-zA-Z0-9+.-]{0,31}:\/\/[^\s'"<>/:@]{1,256}:[^\s'"<>/@]{1,256}@[^\s'"<>]{1,256}/g,
|
|
24
|
+
/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/gi,
|
|
25
|
+
];
|
|
26
|
+
// Redaction also catches unquoted secret-named assignments; too broad to refuse a file on
|
|
27
|
+
// (it matches `password = input.value`), right for scrubbing logs.
|
|
28
|
+
const keywordAssignment = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|authorization)\s*[:=]\s*["']?[A-Za-z0-9._~+/=-]{8,}["']?/gi;
|
|
29
|
+
const secretPatterns = [...shapePatterns, keywordAssignment];
|
|
30
|
+
const homePath = os.homedir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31
|
+
const privatePathPattern = new RegExp(`${homePath}(?:[/\\\\][^\\s/'"]+)*`, "g");
|
|
32
|
+
// Fallback for credentials that don't match a known vendor prefix or keyword (a raw AWS
|
|
33
|
+
// secret access key, an internal hex/base32 API key, a bare high-entropy token): any long
|
|
34
|
+
// run of base64/hex-ish characters with entropy consistent with random data gets redacted,
|
|
35
|
+
// even without a recognizable shape. This is best-effort, not a guarantee — see docs/security.md.
|
|
36
|
+
const highEntropyToken = /[A-Za-z0-9+/_-]{24,}/g;
|
|
37
|
+
const MIN_TOKEN_ENTROPY_BITS_PER_CHAR = 3.5;
|
|
38
|
+
function shannonEntropy(value) {
|
|
39
|
+
const counts = new Map();
|
|
40
|
+
for (const char of value)
|
|
41
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
42
|
+
let entropy = 0;
|
|
43
|
+
for (const count of counts.values()) {
|
|
44
|
+
const p = count / value.length;
|
|
45
|
+
entropy -= p * Math.log2(p);
|
|
46
|
+
}
|
|
47
|
+
return entropy;
|
|
48
|
+
}
|
|
49
|
+
function redactHighEntropyTokens(value) {
|
|
50
|
+
return value.replace(highEntropyToken, (token) => shannonEntropy(token) >= MIN_TOKEN_ENTROPY_BITS_PER_CHAR
|
|
51
|
+
? "[REDACTED]"
|
|
52
|
+
: token);
|
|
53
|
+
}
|
|
54
|
+
// Secret-named assignments, for refusing a file only (too broad for redaction): an env-style
|
|
55
|
+
// line such as DB_PASS=..., or a quoted literal assigned to a secret-named key. Matched one
|
|
56
|
+
// bounded line at a time with bounded runs, so a long or hostile line cannot backtrack.
|
|
57
|
+
const MAX_LINE = 4_096;
|
|
58
|
+
const assignedSecretPatterns = [
|
|
59
|
+
/^[ \t]{0,16}(?:(?:export|ENV|ARG)[ \t]+)?[A-Z0-9_]{0,64}(?:PASS|PASSWORD|PASSWD|PWD|SECRET|TOKEN|API_?KEY|PRIVATE_KEY)[A-Z0-9_]{0,64}[ \t]{0,8}=[ \t]{0,8}\S{6}/,
|
|
60
|
+
/(?:^|[^A-Za-z0-9_])["']?[A-Za-z0-9_]{0,64}(?:secret|token|passw(?:or)?d|pwd|api_?key)[A-Za-z0-9_]{0,64}["']?[ \t]{0,8}[:=][ \t]{0,8}["'][^"'\s]{8,256}["']/i,
|
|
61
|
+
];
|
|
62
|
+
function hasAssignedSecret(value) {
|
|
63
|
+
return value
|
|
64
|
+
.split("\n")
|
|
65
|
+
.some((line) => assignedSecretPatterns.some((pattern) => pattern.test(line.slice(0, MAX_LINE))));
|
|
66
|
+
}
|
|
67
|
+
// Whether the text holds a credential of a known shape or a secret-named literal. No entropy
|
|
68
|
+
// guess and no bare keyword match: used to refuse sending a file, where a false positive on
|
|
69
|
+
// ordinary code would be costly. Pattern-based, so a secret in an unknown shape gets through.
|
|
70
|
+
export function containsKnownSecret(value) {
|
|
71
|
+
return (shapePatterns.some((pattern) => value.match(pattern) !== null) ||
|
|
72
|
+
hasAssignedSecret(value));
|
|
73
|
+
}
|
|
74
|
+
// Pattern replacement only, no length bound — for callers that need to persist a redacted
|
|
75
|
+
// payload of arbitrary size (e.g. `ocean observe`'s captured command output).
|
|
76
|
+
export function redactSecrets(value) {
|
|
77
|
+
let safe = value;
|
|
78
|
+
for (const pattern of secretPatterns)
|
|
79
|
+
safe = safe.replace(pattern, "[REDACTED]");
|
|
80
|
+
safe = redactHighEntropyTokens(safe);
|
|
81
|
+
return safe.replace(privatePathPattern, "[PRIVATE_PATH]");
|
|
82
|
+
}
|
|
83
|
+
export function redactRuntimeText(value) {
|
|
84
|
+
return redactSecrets(value).slice(0, MAX_PAYLOAD);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// The tools a completion client may call in a tool-loop run (T-245 Phase 6), and the pure
|
|
3
|
+
// part of deciding whether one call may run. Filesystem bounds are checked again where the
|
|
4
|
+
// tool executes, against real paths.
|
|
5
|
+
const MAX_PATH = 1_024;
|
|
6
|
+
export const MAX_WRITE_BYTES = 256 * 1024;
|
|
7
|
+
const relativePath = z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(MAX_PATH)
|
|
11
|
+
.refine((value) => !value.includes("\0"), "path contains NUL");
|
|
12
|
+
const toolArgumentSchemas = {
|
|
13
|
+
read_file: z.object({ path: relativePath }).strict(),
|
|
14
|
+
list_directory: z.object({ path: relativePath }).strict(),
|
|
15
|
+
write_file: z
|
|
16
|
+
.object({
|
|
17
|
+
path: relativePath,
|
|
18
|
+
content: z
|
|
19
|
+
.string()
|
|
20
|
+
.refine((value) => Buffer.byteLength(value) <= MAX_WRITE_BYTES, `content exceeds ${MAX_WRITE_BYTES} bytes`),
|
|
21
|
+
})
|
|
22
|
+
.strict(),
|
|
23
|
+
};
|
|
24
|
+
// OpenAI-compatible function specs, as sent to the provider.
|
|
25
|
+
const toolSpecs = {
|
|
26
|
+
read_file: {
|
|
27
|
+
description: "Read a UTF-8 text file. path is relative to the working directory.",
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: { path: { type: "string" } },
|
|
31
|
+
required: ["path"],
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
list_directory: {
|
|
36
|
+
description: "List a directory. path is relative to the working directory; use . for it.",
|
|
37
|
+
parameters: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: { path: { type: "string" } },
|
|
40
|
+
required: ["path"],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
write_file: {
|
|
45
|
+
description: "Create or replace a UTF-8 text file with the full new content. path is relative to the working directory.",
|
|
46
|
+
parameters: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
49
|
+
required: ["path", "content"],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
// Read tools always; write only when the run may write; then narrowed by the run contract.
|
|
55
|
+
// There is no command tool: Ocean cannot confine what a command reads, writes or reaches.
|
|
56
|
+
export function offeredTools(plan, limits) {
|
|
57
|
+
const tools = ["read_file", "list_directory"];
|
|
58
|
+
if (!plan.readOnly)
|
|
59
|
+
tools.push("write_file");
|
|
60
|
+
return tools.filter((tool) => !limits?.denied.includes(tool) &&
|
|
61
|
+
(!limits?.allowed.length || limits.allowed.includes(tool)));
|
|
62
|
+
}
|
|
63
|
+
export function toolDefinitions(plan, limits) {
|
|
64
|
+
return offeredTools(plan, limits).map((name) => ({
|
|
65
|
+
type: "function",
|
|
66
|
+
function: { name, ...toolSpecs[name] },
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
// A call the model asked for is untrusted input: anything not offered to this run or
|
|
70
|
+
// malformed is denied before execution.
|
|
71
|
+
export function decideToolCall(plan, call, limits) {
|
|
72
|
+
if (!(call.name in toolArgumentSchemas))
|
|
73
|
+
return { ok: false, reason: `unknown tool: ${call.name}` };
|
|
74
|
+
const name = call.name;
|
|
75
|
+
if (!offeredTools(plan, limits).includes(name))
|
|
76
|
+
return { ok: false, reason: `tool not allowed in this run: ${name}` };
|
|
77
|
+
let input;
|
|
78
|
+
try {
|
|
79
|
+
input = JSON.parse(call.arguments || "{}");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { ok: false, reason: "arguments are not valid JSON" };
|
|
83
|
+
}
|
|
84
|
+
const parsed = toolArgumentSchemas[name].safeParse(input);
|
|
85
|
+
if (!parsed.success)
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
reason: `invalid arguments: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"} ${issue.message}`).join("; ")}`,
|
|
89
|
+
};
|
|
90
|
+
return { ok: true, name, args: parsed.data };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=agent-tools.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isApiClient, profileIdentity, } from "../profiles/profile.js";
|
|
3
|
+
function deepFreeze(value) {
|
|
4
|
+
if (value && typeof value === "object") {
|
|
5
|
+
for (const child of Object.values(value))
|
|
6
|
+
deepFreeze(child);
|
|
7
|
+
Object.freeze(value);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
// One definition, used by the plan and by the caller that decides whether writes are granted.
|
|
12
|
+
export function usesToolLoop(profile) {
|
|
13
|
+
const binding = profile.clients[profile.provider];
|
|
14
|
+
return (isApiClient(profile.provider) &&
|
|
15
|
+
binding?.transport !== "cli" &&
|
|
16
|
+
binding?.toolLoop === true);
|
|
17
|
+
}
|
|
18
|
+
// `profile` is the result of selectProfileClient: its provider is the selected client.
|
|
19
|
+
export function buildExecutionPlan(profile, options) {
|
|
20
|
+
const client = profile.provider;
|
|
21
|
+
const binding = profile.clients[client];
|
|
22
|
+
const transport = binding?.transport ??
|
|
23
|
+
(isApiClient(client) ? "openai-compatible-api" : "cli");
|
|
24
|
+
if (!options.supportedTransports.includes(transport))
|
|
25
|
+
throw new Error(`Unsupported transport for client ${client}: ${transport}`);
|
|
26
|
+
const memoryNamespace = profile.memory.enabled && profile.memory.scope !== "disabled"
|
|
27
|
+
? profile.name
|
|
28
|
+
: null;
|
|
29
|
+
return deepFreeze({
|
|
30
|
+
profileName: profile.name,
|
|
31
|
+
profileIdentity: profileIdentity(profile),
|
|
32
|
+
client,
|
|
33
|
+
transport,
|
|
34
|
+
provider: client,
|
|
35
|
+
model: profile.model,
|
|
36
|
+
...(binding?.temperature === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { temperature: binding.temperature }),
|
|
39
|
+
toolLoop: usesToolLoop(profile),
|
|
40
|
+
memoryNamespace,
|
|
41
|
+
cwd: options.cwd,
|
|
42
|
+
readOnly: profile.writePolicy === "none" || !options.writeAllowed,
|
|
43
|
+
allowedPaths: [...profile.allowedPaths],
|
|
44
|
+
allowedCommands: [...profile.allowedCommands],
|
|
45
|
+
childEnv: {
|
|
46
|
+
OCEAN_CURRENT_PROFILE: profile.name,
|
|
47
|
+
OCEAN_TARGET_PROVIDER: client,
|
|
48
|
+
OCEAN_TARGET_MODEL: profile.model,
|
|
49
|
+
OCEAN_MEMORY_SCOPE: memoryNamespace ?? "disabled",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function canonical(value) {
|
|
54
|
+
if (Array.isArray(value))
|
|
55
|
+
return value.map(canonical);
|
|
56
|
+
if (value && typeof value === "object")
|
|
57
|
+
return Object.fromEntries(Object.keys(value)
|
|
58
|
+
.sort()
|
|
59
|
+
.map((key) => [
|
|
60
|
+
key,
|
|
61
|
+
canonical(value[key]),
|
|
62
|
+
]));
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
// Keys are sorted recursively, so the identity depends only on the plan's values.
|
|
66
|
+
export function executionPlanIdentity(plan) {
|
|
67
|
+
return createHash("sha256")
|
|
68
|
+
.update(JSON.stringify(canonical(plan)))
|
|
69
|
+
.digest("hex");
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=execution-plan.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const runContractSchema = z
|
|
3
|
+
.object({
|
|
4
|
+
runId: z.string().min(1),
|
|
5
|
+
sessionId: z.string().min(1),
|
|
6
|
+
profile: z.string().min(1),
|
|
7
|
+
workingDirectory: z.string().min(1),
|
|
8
|
+
allowedTools: z.array(z.string().min(1)).default([]),
|
|
9
|
+
deniedTools: z.array(z.string().min(1)).default([]),
|
|
10
|
+
stopConditions: z.array(z.string().min(1)).min(1),
|
|
11
|
+
approval: z.object({ required: z.boolean(), approved: z.boolean() }),
|
|
12
|
+
budget: z.object({
|
|
13
|
+
timeoutMs: z.number().int().positive(),
|
|
14
|
+
maxAttempts: z.number().int().positive(),
|
|
15
|
+
maxOutputBytes: z.number().int().positive(),
|
|
16
|
+
}),
|
|
17
|
+
})
|
|
18
|
+
.superRefine((contract, context) => {
|
|
19
|
+
const overlap = contract.allowedTools.filter((tool) => contract.deniedTools.includes(tool));
|
|
20
|
+
if (overlap.length)
|
|
21
|
+
context.addIssue({
|
|
22
|
+
code: z.ZodIssueCode.custom,
|
|
23
|
+
path: ["deniedTools"],
|
|
24
|
+
message: `Tool is both allowed and denied: ${overlap[0]}`,
|
|
25
|
+
});
|
|
26
|
+
if (contract.approval.required && contract.approval.approved === false)
|
|
27
|
+
return;
|
|
28
|
+
});
|
|
29
|
+
export function validateRunContract(input) {
|
|
30
|
+
return runContractSchema.parse(input);
|
|
31
|
+
}
|
|
32
|
+
export function assertRunCanStart(contract, attempt = 1) {
|
|
33
|
+
if (contract.approval.required && !contract.approval.approved)
|
|
34
|
+
throw new Error("Run approval required");
|
|
35
|
+
if (attempt > contract.budget.maxAttempts)
|
|
36
|
+
throw new Error(`Run attempt limit exceeded: ${attempt}`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=run-contract.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const sessionEntryPointSchema = z.enum([
|
|
3
|
+
"ocean-run",
|
|
4
|
+
"terminal-shim",
|
|
5
|
+
"interactive-managed",
|
|
6
|
+
"desktop-wrapper",
|
|
7
|
+
]);
|
|
8
|
+
const sessionControlLevelSchema = z.enum([
|
|
9
|
+
"full-head",
|
|
10
|
+
"managed-partial",
|
|
11
|
+
"observed",
|
|
12
|
+
"bypass",
|
|
13
|
+
]);
|
|
14
|
+
const sessionInputCaptureSchema = z.enum([
|
|
15
|
+
"semantic",
|
|
16
|
+
"bounded-terminal",
|
|
17
|
+
"none",
|
|
18
|
+
]);
|
|
19
|
+
const sessionEntryContractSchema = z.object({
|
|
20
|
+
entryPoint: sessionEntryPointSchema,
|
|
21
|
+
controlLevel: sessionControlLevelSchema,
|
|
22
|
+
inputCapture: sessionInputCaptureSchema,
|
|
23
|
+
contextTransport: z.string().min(1),
|
|
24
|
+
policyEnforcement: z.string().min(1),
|
|
25
|
+
promotion: z.literal("explicit-review"),
|
|
26
|
+
resume: z.string().min(1),
|
|
27
|
+
});
|
|
28
|
+
export function validateSessionEntryContract(contract) {
|
|
29
|
+
return sessionEntryContractSchema.parse(contract);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=entry-contract.js.map
|