@vellumai/assistant 0.12.2 → 0.12.3-staging.1
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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -1,196 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CES
|
|
2
|
+
* CES socket discovery and transport bootstrap.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Unix socket (`CES_LOCAL_SOCKET`). Used by bare-metal/local instances.
|
|
8
|
-
*
|
|
9
|
-
* 2. **Managed mode** — Locates the bootstrap Unix socket exposed by the CES
|
|
10
|
-
* sidecar container through a shared emptyDir volume, and returns a
|
|
11
|
-
* connected Unix socket transport.
|
|
12
|
-
*
|
|
13
|
-
* Both strategies fail closed: if the socket cannot be found, the discovery
|
|
14
|
-
* returns a structured error rather than falling back to in-process
|
|
15
|
-
* credential handling.
|
|
4
|
+
* Local siblings and managed sidecars share one resolver: `ces.sock` under
|
|
5
|
+
* `CES_BOOTSTRAP_SOCKET_DIR` (default `/run/ces-bootstrap`). Discovery fails
|
|
6
|
+
* closed if the socket cannot be found.
|
|
16
7
|
*/
|
|
17
8
|
|
|
18
9
|
import { existsSync } from "node:fs";
|
|
19
|
-
import { join } from "node:path";
|
|
20
10
|
|
|
21
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
isNamedPipePath,
|
|
13
|
+
resolveIpcEndpoint,
|
|
14
|
+
} from "@vellumai/ipc-server-utils";
|
|
15
|
+
|
|
22
16
|
import { getLogger } from "../util/logger.js";
|
|
23
17
|
|
|
24
18
|
const log = getLogger("ces-discovery");
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
// Well-known paths
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
/** Default directory for the bootstrap socket shared volume. */
|
|
20
|
+
/** Default directory for the CES Unix socket. */
|
|
31
21
|
const DEFAULT_BOOTSTRAP_SOCKET_DIR = "/run/ces-bootstrap";
|
|
32
22
|
|
|
33
|
-
/** Bootstrap socket filename. */
|
|
34
|
-
const BOOTSTRAP_SOCKET_NAME = "ces.sock";
|
|
35
|
-
|
|
36
23
|
/**
|
|
37
|
-
* Resolve the CES
|
|
24
|
+
* Resolve the CES socket path used in every environment.
|
|
38
25
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* 2. `CES_BOOTSTRAP_SOCKET` env var (full file path override)
|
|
42
|
-
* 3. Hardcoded default: `/run/ces-bootstrap/ces.sock`
|
|
43
|
-
*
|
|
44
|
-
* The pod template exports `CES_BOOTSTRAP_SOCKET_DIR`; the full-path
|
|
45
|
-
* override is kept for local testing convenience.
|
|
26
|
+
* Uses `CES_BOOTSTRAP_SOCKET_DIR` when set, otherwise `/run/ces-bootstrap`.
|
|
27
|
+
* `ces.sock` is resolved through `resolveIpcEndpoint`.
|
|
46
28
|
*/
|
|
47
|
-
function
|
|
48
|
-
const dir =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
return (
|
|
53
|
-
process.env["CES_BOOTSTRAP_SOCKET"] ??
|
|
54
|
-
join(DEFAULT_BOOTSTRAP_SOCKET_DIR, BOOTSTRAP_SOCKET_NAME)
|
|
55
|
-
);
|
|
29
|
+
function getCesSocketPath(): string {
|
|
30
|
+
const dir =
|
|
31
|
+
process.env["CES_BOOTSTRAP_SOCKET_DIR"]?.trim() ||
|
|
32
|
+
DEFAULT_BOOTSTRAP_SOCKET_DIR;
|
|
33
|
+
return resolveIpcEndpoint("ces", { workspaceDir: dir }).path;
|
|
56
34
|
}
|
|
57
35
|
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Discovery result types
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
|
|
62
36
|
export interface ManagedDiscoverySuccess {
|
|
63
37
|
mode: "managed";
|
|
64
38
|
socketPath: string;
|
|
65
39
|
}
|
|
66
40
|
|
|
67
|
-
/**
|
|
68
|
-
* A local CES running as an independent sibling process (not spawned by the
|
|
69
|
-
* assistant), reached over a Unix socket. Transport-identical to managed; the
|
|
70
|
-
* distinct mode records that the assistant does not own this process's
|
|
71
|
-
* lifecycle. This is the default topology for bare-metal/local instances.
|
|
72
|
-
*/
|
|
73
|
-
export interface SiblingDiscoverySuccess {
|
|
74
|
-
mode: "sibling";
|
|
75
|
-
socketPath: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
41
|
export interface DiscoveryFailure {
|
|
79
42
|
mode: "unavailable";
|
|
80
43
|
reason: string;
|
|
81
44
|
}
|
|
82
45
|
|
|
83
|
-
export type DiscoveryResult =
|
|
84
|
-
| ManagedDiscoverySuccess
|
|
85
|
-
| SiblingDiscoverySuccess
|
|
86
|
-
| DiscoveryFailure;
|
|
87
|
-
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Managed discovery
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
46
|
+
export type DiscoveryResult = ManagedDiscoverySuccess | DiscoveryFailure;
|
|
91
47
|
|
|
92
48
|
/**
|
|
93
|
-
* Discover
|
|
49
|
+
* Discover CES via the shared bootstrap Unix socket.
|
|
94
50
|
*
|
|
95
|
-
* Checks that the well-known socket
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* The socket path is derived from `CES_BOOTSTRAP_SOCKET_DIR` (matching the
|
|
101
|
-
* pod template), with `CES_BOOTSTRAP_SOCKET` as a full-path fallback.
|
|
51
|
+
* Checks that the well-known socket exists. Does not open a connection.
|
|
52
|
+
* CES serves a multi-connection listener, so the assistant and its child
|
|
53
|
+
* processes can each connect. The actual connection is made later by
|
|
54
|
+
* `CesProcessManager.start()`.
|
|
102
55
|
*/
|
|
103
56
|
export function discoverManagedCes():
|
|
104
57
|
| ManagedDiscoverySuccess
|
|
105
58
|
| DiscoveryFailure {
|
|
106
|
-
const socketPath =
|
|
59
|
+
const socketPath = getCesSocketPath();
|
|
107
60
|
|
|
108
|
-
if (!existsSync(socketPath)) {
|
|
61
|
+
if (!isNamedPipePath(socketPath) && !existsSync(socketPath)) {
|
|
109
62
|
const reason = `CES bootstrap socket not found at ${socketPath}`;
|
|
110
63
|
log.warn(reason);
|
|
111
64
|
return { mode: "unavailable", reason };
|
|
112
65
|
}
|
|
113
66
|
|
|
114
|
-
log.info({ socketPath }, "
|
|
67
|
+
log.info({ socketPath }, "CES bootstrap socket found");
|
|
115
68
|
return { mode: "managed", socketPath };
|
|
116
69
|
}
|
|
117
70
|
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Sibling discovery (local bare-metal topology)
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
71
|
/**
|
|
123
|
-
* Discover
|
|
124
|
-
*
|
|
125
|
-
* path. Does not open a connection — that happens in `CesProcessManager.start()`.
|
|
126
|
-
*/
|
|
127
|
-
export function discoverLocalSiblingCes():
|
|
128
|
-
| SiblingDiscoverySuccess
|
|
129
|
-
| DiscoveryFailure {
|
|
130
|
-
const socketPath = process.env["CES_LOCAL_SOCKET"];
|
|
131
|
-
if (!socketPath) {
|
|
132
|
-
const reason =
|
|
133
|
-
"CES_LOCAL_SOCKET is not set — cannot locate the CES sibling socket. The CLI should set this during wake/hatch.";
|
|
134
|
-
log.warn(reason);
|
|
135
|
-
return { mode: "unavailable", reason };
|
|
136
|
-
}
|
|
137
|
-
if (!existsSync(socketPath)) {
|
|
138
|
-
return {
|
|
139
|
-
mode: "unavailable",
|
|
140
|
-
reason: `CES sibling socket not found at ${socketPath}`,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
log.info({ socketPath }, "Local CES sibling socket found");
|
|
144
|
-
return { mode: "sibling", socketPath };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// Auto-discovery
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Automatically discover CES using the appropriate strategy for the
|
|
153
|
-
* current deployment topology.
|
|
154
|
-
*
|
|
155
|
-
* - Containerized environments → managed sidecar discovery
|
|
156
|
-
* - Non-containerized → CLI-launched sibling socket discovery
|
|
72
|
+
* Discover CES for the current process. Local and managed topologies use
|
|
73
|
+
* the same bootstrap socket path.
|
|
157
74
|
*/
|
|
158
75
|
export function discoverCes(): DiscoveryResult {
|
|
159
|
-
|
|
160
|
-
return discoverManagedCes();
|
|
161
|
-
}
|
|
162
|
-
return discoverLocalSiblingCes();
|
|
76
|
+
return discoverManagedCes();
|
|
163
77
|
}
|
|
164
78
|
|
|
165
|
-
/** How long to poll for the
|
|
79
|
+
/** How long to poll for the CES socket before giving up. */
|
|
166
80
|
const MANAGED_DISCOVERY_TIMEOUT_MS = 3_000;
|
|
167
81
|
|
|
168
|
-
/** Delay between
|
|
82
|
+
/** Delay between CES socket discovery attempts. */
|
|
169
83
|
const MANAGED_DISCOVERY_INTERVAL_MS = 100;
|
|
170
84
|
|
|
171
85
|
/**
|
|
172
|
-
* Discover CES, polling for the
|
|
173
|
-
* backoff before failing.
|
|
174
|
-
*
|
|
175
|
-
* The managed CES sidecar re-binds its bootstrap socket after each assistant
|
|
176
|
-
* session ends — it outlives any single assistant and accepts the next
|
|
177
|
-
* connection (see `credential-executor/src/main.ts`). A reconnecting
|
|
178
|
-
* assistant (e.g. after a container restart) can therefore probe during the
|
|
179
|
-
* brief window before the socket is re-bound. A single existence check would
|
|
180
|
-
* race that window and incorrectly report the sidecar as unavailable; polling
|
|
181
|
-
* absorbs the gap.
|
|
86
|
+
* Discover CES, polling for the socket with a short backoff before failing.
|
|
182
87
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
88
|
+
* CES binds its socket asynchronously. A reconnecting assistant can probe
|
|
89
|
+
* during the brief window before the socket is re-bound. Polling absorbs
|
|
90
|
+
* that gap.
|
|
185
91
|
*/
|
|
186
92
|
export async function discoverCesWithRetry({
|
|
187
93
|
timeoutMs = MANAGED_DISCOVERY_TIMEOUT_MS,
|
|
188
94
|
intervalMs = MANAGED_DISCOVERY_INTERVAL_MS,
|
|
189
95
|
}: { timeoutMs?: number; intervalMs?: number } = {}): Promise<DiscoveryResult> {
|
|
190
|
-
// Both the managed bootstrap socket and the CLI-launched sibling socket
|
|
191
|
-
// are bound asynchronously, so polling is worthwhile. A missing local
|
|
192
|
-
// binary (the old stdio path) would not appear by waiting, but that path
|
|
193
|
-
// is gone — the sibling is the only local topology now.
|
|
194
96
|
const deadline = Date.now() + timeoutMs;
|
|
195
97
|
let result = discoverCes();
|
|
196
98
|
while (result.mode === "unavailable" && Date.now() < deadline) {
|
|
@@ -3,12 +3,10 @@ import { createServer, type Server } from "node:net";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
|
-
afterAll,
|
|
7
6
|
afterEach,
|
|
8
7
|
beforeEach,
|
|
9
8
|
describe,
|
|
10
9
|
expect,
|
|
11
|
-
mock,
|
|
12
10
|
test,
|
|
13
11
|
} from "bun:test";
|
|
14
12
|
|
|
@@ -21,22 +19,18 @@ const untilClosed = (predicate: () => boolean) =>
|
|
|
21
19
|
message: "socket close never propagated to the transport",
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// onTransportClose tests
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
// Mock executable-discovery so start() connects to our test socket.
|
|
29
|
-
const realDiscovery = await import("./executable-discovery.js");
|
|
30
|
-
|
|
31
22
|
let mockSocketPath = "";
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
mode: "sibling" as const,
|
|
24
|
+
function discoverTestSocket() {
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
mode: "managed" as const,
|
|
37
27
|
socketPath: mockSocketPath,
|
|
38
|
-
})
|
|
39
|
-
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// onTransportClose tests
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
40
34
|
|
|
41
35
|
describe("CesProcessManager.onTransportClose", () => {
|
|
42
36
|
let tempDir: string;
|
|
@@ -67,12 +61,8 @@ describe("CesProcessManager.onTransportClose", () => {
|
|
|
67
61
|
rmSync(tempDir, { recursive: true, force: true });
|
|
68
62
|
});
|
|
69
63
|
|
|
70
|
-
afterAll(() => {
|
|
71
|
-
mock.module("./executable-discovery.js", () => realDiscovery);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
64
|
test("fires handler when the transport dies (socket closed)", async () => {
|
|
75
|
-
const pm = createCesProcessManager({});
|
|
65
|
+
const pm = createCesProcessManager({ discover: discoverTestSocket });
|
|
76
66
|
const transport = await pm.start();
|
|
77
67
|
expect(transport.isAlive()).toBe(true);
|
|
78
68
|
|
|
@@ -93,7 +83,7 @@ describe("CesProcessManager.onTransportClose", () => {
|
|
|
93
83
|
});
|
|
94
84
|
|
|
95
85
|
test("does not fire handler before the transport dies", async () => {
|
|
96
|
-
const pm = createCesProcessManager({});
|
|
86
|
+
const pm = createCesProcessManager({ discover: discoverTestSocket });
|
|
97
87
|
await pm.start();
|
|
98
88
|
|
|
99
89
|
let closeFired = false;
|
|
@@ -109,7 +99,7 @@ describe("CesProcessManager.onTransportClose", () => {
|
|
|
109
99
|
});
|
|
110
100
|
|
|
111
101
|
test("fires handler immediately if transport is already dead", async () => {
|
|
112
|
-
const pm = createCesProcessManager({});
|
|
102
|
+
const pm = createCesProcessManager({ discover: discoverTestSocket });
|
|
113
103
|
const transport = await pm.start();
|
|
114
104
|
|
|
115
105
|
// Kill the transport by destroying the server-side socket.
|
|
@@ -186,7 +176,10 @@ describe("CesProcessManager transport-death logging", () => {
|
|
|
186
176
|
|
|
187
177
|
test("logs WARN when the transport dies unexpectedly (remote close)", async () => {
|
|
188
178
|
const { calls, logger } = makeRecordingLogger();
|
|
189
|
-
const pm = createCesProcessManager({
|
|
179
|
+
const pm = createCesProcessManager({
|
|
180
|
+
logger,
|
|
181
|
+
discover: discoverTestSocket,
|
|
182
|
+
});
|
|
190
183
|
const transport = await pm.start();
|
|
191
184
|
|
|
192
185
|
// The remote (server) closes the connection: genuinely-unexpected death.
|
|
@@ -205,7 +198,10 @@ describe("CesProcessManager transport-death logging", () => {
|
|
|
205
198
|
|
|
206
199
|
test("does not log WARN on an intentional stop()", async () => {
|
|
207
200
|
const { calls, logger } = makeRecordingLogger();
|
|
208
|
-
const pm = createCesProcessManager({
|
|
201
|
+
const pm = createCesProcessManager({
|
|
202
|
+
logger,
|
|
203
|
+
discover: discoverTestSocket,
|
|
204
|
+
});
|
|
209
205
|
await pm.start();
|
|
210
206
|
|
|
211
207
|
await pm.stop();
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CES process manager.
|
|
3
3
|
*
|
|
4
|
-
* Creates transport
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Creates a transport connection to the CES process over the shared bootstrap
|
|
5
|
+
* socket. Local CLI siblings and managed sidecars bind the same path under
|
|
6
|
+
* `CES_BOOTSTRAP_SOCKET_DIR`. The process manager owns only the transport
|
|
7
|
+
* connection lifecycle; the CES process itself is managed by the CLI
|
|
8
|
+
* (sibling) or the pod infrastructure (managed).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* - /assistant-data-ro
|
|
13
|
-
* - /ces-data
|
|
14
|
-
* - CES_HEALTH_PORT
|
|
10
|
+
* Env contract:
|
|
11
|
+
* - CES_BOOTSTRAP_SOCKET_DIR: directory containing `ces.sock`
|
|
12
|
+
* - /assistant-data-ro: assistant data mounted read-only into CES
|
|
13
|
+
* - /ces-data: CES private data directory (separate PVC)
|
|
14
|
+
* - CES_HEALTH_PORT: health check port exposed by the CES sidecar
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { createConnection, type Socket } from "node:net";
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
discoverCesWithRetry,
|
|
25
25
|
type DiscoveryResult,
|
|
26
26
|
type ManagedDiscoverySuccess,
|
|
27
|
-
type SiblingDiscoverySuccess,
|
|
28
27
|
} from "./executable-discovery.js";
|
|
29
28
|
|
|
30
29
|
const log = getLogger("ces-process-manager");
|
|
@@ -62,6 +61,12 @@ export interface CesProcessManagerConfig {
|
|
|
62
61
|
|
|
63
62
|
/** Logger override. Defaults to the module logger; injected in tests. */
|
|
64
63
|
logger?: PmLogger;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Socket discovery. Defaults to `discoverCesWithRetry`. Tests inject a
|
|
67
|
+
* stub that returns a known socket path.
|
|
68
|
+
*/
|
|
69
|
+
discover?: typeof discoverCesWithRetry;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
// ---------------------------------------------------------------------------
|
|
@@ -70,7 +75,7 @@ export interface CesProcessManagerConfig {
|
|
|
70
75
|
|
|
71
76
|
export interface CesProcessManager {
|
|
72
77
|
/**
|
|
73
|
-
* Connect to the CES
|
|
78
|
+
* Connect to the CES bootstrap socket.
|
|
74
79
|
* Returns a CesTransport ready for use with createCesClient().
|
|
75
80
|
*
|
|
76
81
|
* Throws if CES is unavailable.
|
|
@@ -107,6 +112,7 @@ export function createCesProcessManager(
|
|
|
107
112
|
config: CesProcessManagerConfig,
|
|
108
113
|
): CesProcessManager {
|
|
109
114
|
const pmLog = config.logger ?? log;
|
|
115
|
+
const discover = config.discover ?? discoverCesWithRetry;
|
|
110
116
|
let managedSocket: Socket | null = null;
|
|
111
117
|
let discoveryResult: DiscoveryResult | null = null;
|
|
112
118
|
let running = false;
|
|
@@ -119,16 +125,15 @@ export function createCesProcessManager(
|
|
|
119
125
|
throw new Error("CES process manager is already running");
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
// Poll for the socket with a short backoff.
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
discoveryResult = await
|
|
128
|
+
// Poll for the socket with a short backoff. CES binds
|
|
129
|
+
// asynchronously, so a reconnecting assistant can briefly race
|
|
130
|
+
// the re-bind.
|
|
131
|
+
discoveryResult = await discover();
|
|
126
132
|
|
|
127
133
|
if (discoveryResult.mode === "unavailable") {
|
|
128
134
|
throw new CesUnavailableError(discoveryResult.reason);
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
// managed sidecar or CLI-launched sibling — both connect to a socket.
|
|
132
137
|
const transport = await connectManagedSocket(discoveryResult);
|
|
133
138
|
currentTransport = transport;
|
|
134
139
|
wireTransportClose(transport);
|
|
@@ -200,11 +205,11 @@ export function createCesProcessManager(
|
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
// -------------------------------------------------------------------------
|
|
203
|
-
// Socket connection
|
|
208
|
+
// Socket connection
|
|
204
209
|
// -------------------------------------------------------------------------
|
|
205
210
|
|
|
206
211
|
async function connectManagedSocket(
|
|
207
|
-
discovery: ManagedDiscoverySuccess
|
|
212
|
+
discovery: ManagedDiscoverySuccess,
|
|
208
213
|
): Promise<SocketTransport> {
|
|
209
214
|
pmLog.info(
|
|
210
215
|
{ socketPath: discovery.socketPath, mode: discovery.mode },
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
8
|
|
|
9
|
+
import { CONSOLIDATION_ALLOWED_TOOLS } from "../../plugins/defaults/memory/substrate/consolidation-tool-surface.js";
|
|
9
10
|
import type { Conversation } from "../conversation.js";
|
|
10
11
|
import type { ChannelCapabilities } from "../conversation-runtime-assembly.js";
|
|
11
12
|
|
|
@@ -779,3 +780,45 @@ describe("isToolActiveForContext — allowlist-only tools (delete_memory_page)",
|
|
|
779
780
|
).toBe(false);
|
|
780
781
|
});
|
|
781
782
|
});
|
|
783
|
+
|
|
784
|
+
describe("isToolActiveForContext — memory consolidation surface resolves onto the wire", () => {
|
|
785
|
+
// A background job conversation is not a subagent, so SUBAGENT_ONLY tools
|
|
786
|
+
// are filtered off before the allowlist applies. Every name the
|
|
787
|
+
// consolidation run allowlists must survive that gate, or the tool is
|
|
788
|
+
// silently missing from the run while the allowlist still claims it.
|
|
789
|
+
const consolidationCtx = () =>
|
|
790
|
+
makeCtx({
|
|
791
|
+
subagentAllowedTools: new Set(CONSOLIDATION_ALLOWED_TOOLS),
|
|
792
|
+
subagentToolGateMode: "wire",
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("every allowlisted tool is active for a background conversation", () => {
|
|
796
|
+
for (const name of CONSOLIDATION_ALLOWED_TOOLS) {
|
|
797
|
+
expect(isToolActiveForContext(name, consolidationCtx()), name).toBe(true);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("the shell and the page-delete primitive are on the surface", () => {
|
|
802
|
+
expect(isToolActiveForContext("bash", consolidationCtx())).toBe(true);
|
|
803
|
+
expect(
|
|
804
|
+
isToolActiveForContext("delete_memory_page", consolidationCtx()),
|
|
805
|
+
).toBe(true);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test("network egress and host-proxy tools stay off the wire", () => {
|
|
809
|
+
for (const name of [
|
|
810
|
+
"web_fetch",
|
|
811
|
+
"web_search",
|
|
812
|
+
"network_request",
|
|
813
|
+
"host_bash",
|
|
814
|
+
"host_file_read",
|
|
815
|
+
"host_file_write",
|
|
816
|
+
"host_file_edit",
|
|
817
|
+
"host_cu",
|
|
818
|
+
]) {
|
|
819
|
+
expect(isToolActiveForContext(name, consolidationCtx()), name).toBe(
|
|
820
|
+
false,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
});
|
|
@@ -128,6 +128,7 @@ async function runSettle(): Promise<Message[]> {
|
|
|
128
128
|
ctx,
|
|
129
129
|
state: {
|
|
130
130
|
lastAssistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
131
|
+
assistantMessageIdsToSync: new Set(),
|
|
131
132
|
inflightWriters: new Map<string, InflightContentWriter>(),
|
|
132
133
|
},
|
|
133
134
|
rlog,
|
|
@@ -169,4 +170,39 @@ describe("settleTurnContent conversation deleted after the terminal SSE", () =>
|
|
|
169
170
|
expect(diskSyncCalls).toEqual([ASSISTANT_MESSAGE_ID]);
|
|
170
171
|
expect(messages).toEqual(TRUNCATED_MESSAGES);
|
|
171
172
|
});
|
|
173
|
+
|
|
174
|
+
test("exports a queued attachment target only once when it is the final row", async () => {
|
|
175
|
+
const ctx = { conversationId: CONVERSATION_ID, messages: [] as Message[] };
|
|
176
|
+
|
|
177
|
+
await settleTurnContent({
|
|
178
|
+
ctx,
|
|
179
|
+
state: {
|
|
180
|
+
lastAssistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
181
|
+
assistantMessageIdsToSync: new Set([ASSISTANT_MESSAGE_ID]),
|
|
182
|
+
inflightWriters: new Map<string, InflightContentWriter>(),
|
|
183
|
+
},
|
|
184
|
+
rlog,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(diskSyncCalls).toEqual([ASSISTANT_MESSAGE_ID]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("exports an earlier attachment target before the final private row", async () => {
|
|
191
|
+
const ctx = { conversationId: CONVERSATION_ID, messages: [] as Message[] };
|
|
192
|
+
|
|
193
|
+
await settleTurnContent({
|
|
194
|
+
ctx,
|
|
195
|
+
state: {
|
|
196
|
+
lastAssistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
197
|
+
assistantMessageIdsToSync: new Set(["msg-delivered-reply"]),
|
|
198
|
+
inflightWriters: new Map<string, InflightContentWriter>(),
|
|
199
|
+
},
|
|
200
|
+
rlog,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(diskSyncCalls).toEqual([
|
|
204
|
+
"msg-delivered-reply",
|
|
205
|
+
ASSISTANT_MESSAGE_ID,
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
172
208
|
});
|
|
@@ -109,8 +109,8 @@ export function classifyKind(mimeType: string): "image" | "video" | "document" {
|
|
|
109
109
|
// Validation / cap enforcement
|
|
110
110
|
// ---------------------------------------------------------------------------
|
|
111
111
|
|
|
112
|
-
interface ValidatedDrafts {
|
|
113
|
-
accepted:
|
|
112
|
+
interface ValidatedDrafts<T extends AssistantAttachmentDraft> {
|
|
113
|
+
accepted: T[];
|
|
114
114
|
warnings: string[];
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -119,10 +119,10 @@ interface ValidatedDrafts {
|
|
|
119
119
|
*
|
|
120
120
|
* - Rejects individual drafts that exceed `MAX_ASSISTANT_ATTACHMENT_BYTES`.
|
|
121
121
|
*/
|
|
122
|
-
export function validateDrafts(
|
|
123
|
-
drafts:
|
|
124
|
-
): ValidatedDrafts {
|
|
125
|
-
const accepted:
|
|
122
|
+
export function validateDrafts<T extends AssistantAttachmentDraft>(
|
|
123
|
+
drafts: T[],
|
|
124
|
+
): ValidatedDrafts<T> {
|
|
125
|
+
const accepted: T[] = [];
|
|
126
126
|
const warnings: string[] = [];
|
|
127
127
|
|
|
128
128
|
for (const draft of drafts) {
|
|
@@ -800,6 +800,16 @@ function toolNameToFilePrefix(toolName?: string): string {
|
|
|
800
800
|
.toLowerCase();
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
+
/** Shared download name for an image produced by a tool result. */
|
|
804
|
+
export function toolImageFilename(
|
|
805
|
+
mediaType: string,
|
|
806
|
+
toolName?: string,
|
|
807
|
+
title?: string,
|
|
808
|
+
): string {
|
|
809
|
+
const ext = mediaType.split("/")[1] ?? "png";
|
|
810
|
+
return `${title || toolNameToFilePrefix(toolName)}.${ext}`;
|
|
811
|
+
}
|
|
812
|
+
|
|
803
813
|
/**
|
|
804
814
|
* Convert tool content blocks (images/files from tool results) into
|
|
805
815
|
* attachment drafts. Blocks that aren't image or file types are skipped.
|
|
@@ -820,12 +830,10 @@ export function contentBlocksToDrafts(
|
|
|
820
830
|
const src = b.source as ImageBlock["source"];
|
|
821
831
|
const data = src.data;
|
|
822
832
|
const mimeType = src.media_type;
|
|
823
|
-
const ext = mimeType.split("/")[1] ?? "png";
|
|
824
833
|
const title = typeof b._title === "string" ? b._title : undefined;
|
|
825
|
-
const prefix = title || toolNameToFilePrefix(toolName);
|
|
826
834
|
drafts.push({
|
|
827
835
|
sourceType: "tool_block",
|
|
828
|
-
filename:
|
|
836
|
+
filename: toolImageFilename(mimeType, toolName, title),
|
|
829
837
|
mimeType,
|
|
830
838
|
dataBase64: data,
|
|
831
839
|
sizeBytes: estimateBase64Bytes(data),
|
|
@@ -918,9 +926,9 @@ export function cleanAssistantContent(content: readonly unknown[]): {
|
|
|
918
926
|
* data from being attached twice when it appears as both a directive tag
|
|
919
927
|
* (user-chosen name) and an auto-converted tool block ("tool-output.png").
|
|
920
928
|
*/
|
|
921
|
-
export function deduplicateDrafts(
|
|
922
|
-
drafts:
|
|
923
|
-
):
|
|
929
|
+
export function deduplicateDrafts<T extends AssistantAttachmentDraft>(
|
|
930
|
+
drafts: T[],
|
|
931
|
+
): T[] {
|
|
924
932
|
const seenKeys = new Set<string>();
|
|
925
933
|
const seenDirectiveHashes = new Set<string>();
|
|
926
934
|
return drafts.filter((d) => {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 1. While a turn runs, every shell-style tool command is scanned for
|
|
12
12
|
* `credentials reveal` invocations and the referenced credentials
|
|
13
|
-
* (`--service`/`--field` flags
|
|
13
|
+
* (`--service`/`--field` flags, a positional UUID, or a legacy
|
|
14
|
+
* `service/field` path) are recorded as
|
|
14
15
|
* *candidates* ({@link collectRevealRefsFromCommand}).
|
|
15
16
|
* 2. At persist time, only those candidates' plaintexts are fetched from
|
|
16
17
|
* the credential store — a scoped read of secrets that were already in
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
55
56
|
} from "../security/secret-scanner.js";
|
|
56
57
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
57
58
|
import { getCredentialMetadataById } from "../tools/credentials/metadata-store.js";
|
|
59
|
+
import { parseServiceFieldRef } from "../tools/credentials/ref-parse.js";
|
|
58
60
|
import { getLogger } from "../util/logger.js";
|
|
59
61
|
|
|
60
62
|
const log = getLogger("chat-credential-redaction");
|
|
@@ -106,6 +108,12 @@ const SERVICE_FLAG_RE = new RegExp(String.raw`--service(?:=|\s+)${FLAG_VALUE}`);
|
|
|
106
108
|
const FIELD_FLAG_RE = new RegExp(String.raw`--field(?:=|\s+)${FLAG_VALUE}`);
|
|
107
109
|
const UUID_RE =
|
|
108
110
|
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
|
111
|
+
// The CLI's single positional argument, directly after `reveal`. A UUID is
|
|
112
|
+
// handled above; anything else is tried as a legacy `service/field` path via
|
|
113
|
+
// the shared grammar in `parseServiceFieldRef`. The route rejects that form
|
|
114
|
+
// as an id today, so no plaintext prints; staging the ref is defense in depth
|
|
115
|
+
// so a future lookup that does succeed is already proof-gated.
|
|
116
|
+
const POSITIONAL_RE = /\breveal\s+([^\s"'\\()<>`-][^\s"'\\()<>`]*)/;
|
|
109
117
|
|
|
110
118
|
/**
|
|
111
119
|
* Extract a flag's value from a segment, undoing the shell quoting the
|
|
@@ -265,6 +273,13 @@ export function collectRevealRefsFromCommand(
|
|
|
265
273
|
const id = UUID_RE.exec(invocation)?.[0];
|
|
266
274
|
if (id) {
|
|
267
275
|
refs.push({ id });
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const positional = POSITIONAL_RE.exec(invocation)?.[1];
|
|
279
|
+
const pathRef =
|
|
280
|
+
positional === undefined ? undefined : parseServiceFieldRef(positional);
|
|
281
|
+
if (pathRef) {
|
|
282
|
+
refs.push(pathRef);
|
|
268
283
|
}
|
|
269
284
|
// No parseable identity → no ref. The span still gets redacted at
|
|
270
285
|
// persist; it just won't be revealable.
|