mixdog 1.2.6 → 1.3.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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +71 -9
- package/package.json +4 -1
- package/scripts/check-model-recommendations.mjs +89 -0
- package/scripts/gate-local.mjs +7 -0
- package/scripts/lib/isolated-test-env.mjs +16 -0
- package/src/agents/reviewer/AGENT.md +4 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +5 -0
- package/src/defaults/skills/memory-management/SKILL.md +4 -1
- package/src/defaults/skills/setup/references/actions.md +14 -4
- package/src/defaults/skills/setup-guide/SKILL.md +178 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent/AGENT.md +4 -0
- package/src/rules/agent-mode/AGENT_MODE.md +24 -0
- package/src/rules/agent-mode/MIXY.md +28 -0
- package/src/rules/agent-mode/ROUTINE.md +6 -0
- package/src/rules/lead/LEAD.md +7 -3
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
- package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
- package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
- package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/browser-bridge/client.mjs +24 -0
- package/src/runtime/channels/lib/scheduler.mjs +40 -12
- package/src/runtime/channels/lib/schedules-db.mjs +71 -12
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
- package/src/runtime/channels/lib/webhook.mjs +0 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
- package/src/runtime/channels/lib/worker-main.mjs +17 -20
- package/src/runtime/computer-bridge/client.mjs +28 -1
- package/src/runtime/connectors/gmail-app-password.mjs +690 -0
- package/src/runtime/connectors/google-mcp.mjs +446 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
- package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
- package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
- package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
- package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
- package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
- package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
- package/src/runtime/memory/lib/memory.mjs +7 -1
- package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
- package/src/runtime/memory/tool-defs.mjs +5 -0
- package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
- package/src/runtime/shared/agent-avatar.d.mts +64 -0
- package/src/runtime/shared/agent-avatar.mjs +320 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.mjs +30 -0
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/atomic-file.mjs +14 -1
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +8 -1
- package/src/runtime/shared/connector-accounts.mjs +15 -0
- package/src/runtime/shared/connector-catalog.d.mts +51 -0
- package/src/runtime/shared/connector-catalog.mjs +228 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +45 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/google-oauth-client.mjs +24 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/held-request-host.d.mts +12 -0
- package/src/runtime/shared/held-request-host.mjs +71 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +33 -0
- package/src/runtime/shared/routine-limits.d.mts +11 -0
- package/src/runtime/shared/routine-limits.mjs +21 -0
- package/src/runtime/shared/routine-trigger.d.mts +10 -0
- package/src/runtime/shared/routine-trigger.mjs +44 -0
- package/src/runtime/shared/schedule-model-ref.mjs +9 -10
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/slow-span.mjs +4 -12
- package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
- package/src/runtime/shared/transcript-metadata.mjs +10 -0
- package/src/runtime/shared/turn-changes.mjs +0 -8
- package/src/runtime/shared/usage-readout-totals.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +119 -38
- package/src/session-runtime/agent-home-delivery.mjs +826 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +133 -0
- package/src/session-runtime/agent-task-sessions.mjs +460 -0
- package/src/session-runtime/boot/apis.mjs +63 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +228 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
- package/src/session-runtime/lifecycle/shared.mjs +6 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
- package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +112 -15
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +35 -4
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +229 -0
- package/src/session-runtime/resource-api.mjs +5 -0
- package/src/session-runtime/resource-mcp-api.mjs +107 -13
- package/src/session-runtime/resource-skills-api.mjs +12 -0
- package/src/session-runtime/room-member-gate.mjs +48 -0
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/routine-home-turn.mjs +156 -0
- package/src/session-runtime/routine-owner.mjs +48 -0
- package/src/session-runtime/schedule-session-run.mjs +30 -64
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/channel-admin.mjs +93 -24
- package/src/session-runtime/services/memory-access.mjs +37 -0
- package/src/session-runtime/services/provider-admin.mjs +74 -39
- package/src/session-runtime/services/schedule-access.mjs +91 -0
- package/src/session-runtime/session-create/session-options.mjs +34 -7
- package/src/session-runtime/session-hooks.mjs +16 -8
- package/src/session-runtime/setup-tool/executor.mjs +79 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
- package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/skills-api.mjs +16 -1
- package/src/session-runtime/tool-policy-refresh.mjs +249 -29
- package/src/session-runtime/turn/session-ops.mjs +8 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
- package/src/session-runtime/webhook-session-run.mjs +49 -67
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
- package/src/session-runtime/workflow-agents-api.mjs +4 -1
- package/src/standalone/daemon-agent-control.mjs +1 -8
- package/src/standalone/daemon-session-control.mjs +21 -4
- package/src/standalone/daemon.mjs +31 -5
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +234 -95
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +34 -4
- package/src/tui/session/queue-helpers.mjs +2 -1
- package/src/tui/session/session-api/extensions.mjs +4 -0
- package/src/tui/session/session-api/intake/submission.mjs +5 -0
- package/src/tui/session/session-api/integrations.mjs +8 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
- package/src/tui/session/session-api/lifecycle.mjs +17 -1
- package/src/tui/session/session-api/routes.mjs +61 -5
- package/src/tui/session/session-flow/drain.mjs +12 -3
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
- package/src/tui/session/session-flow/steering.mjs +58 -24
- package/src/tui/session/tool-approval.mjs +26 -5
- package/src/tui/session/transcript-intake.mjs +8 -2
- package/src/tui/session/transcript-spill.mjs +5 -1
- package/src/tui/session/tui-steering-persist.mjs +65 -42
- package/src/tui/session/turn-live-call.mjs +64 -0
- package/src/tui/session/turn.mjs +34 -8
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Held requests: unattended runs never auto-approve. In a schedule, webhook or
|
|
3
|
+
* group-room-member run, an approval-needing call is HELD instead of shown as
|
|
4
|
+
* a card: the run pauses on it, a structured held-request row lands in the
|
|
5
|
+
* owning agent's home thread (agent-home-delivery.mjs queue), a phone push
|
|
6
|
+
* says only "<agent>가 승인을 기다립니다" (no tool, arguments or text), and the
|
|
7
|
+
* request expires after HELD_REQUEST_EXPIRY_MS into a deny, so the run goes
|
|
8
|
+
* on with the denial. The user answers from the home thread through the
|
|
9
|
+
* ordinary approval call with the held id (runtime/shared/held-request-host.mjs).
|
|
10
|
+
*
|
|
11
|
+
* Importing this module installs the unattended port of human-gate/gate.mjs.
|
|
12
|
+
* Rooms: a room member session counts as unattended by its id; a surface that
|
|
13
|
+
* hosts its own approval UI (a room) keeps it, and may call holdApprovalRequest
|
|
14
|
+
* itself to hold one request for the member's agent.
|
|
15
|
+
*/
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import { MIXY_AGENT_ID, agentHomeSessionId } from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
18
|
+
import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
19
|
+
import { mixyDisplayName } from '../runtime/agent/orchestrator/session/mixy-name.mjs';
|
|
20
|
+
import {
|
|
21
|
+
recordHumanDecision,
|
|
22
|
+
setUnattendedApprovalPort,
|
|
23
|
+
} from '../runtime/agent/orchestrator/session/human-gate/gate.mjs';
|
|
24
|
+
import { normalizeHumanResponse } from '../runtime/agent/orchestrator/session/human-gate/requests.mjs';
|
|
25
|
+
import { normalizeHeldRequest } from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
|
|
26
|
+
import {
|
|
27
|
+
notifyHeldRequest,
|
|
28
|
+
recordRunOwner,
|
|
29
|
+
registerHeldRequestResolver,
|
|
30
|
+
} from '../runtime/shared/held-request-host.mjs';
|
|
31
|
+
|
|
32
|
+
/** Default expiry; MIXDOG_HELD_REQUEST_EXPIRY_MS overrides it. */
|
|
33
|
+
export const HELD_REQUEST_EXPIRY_MS = 5 * 60_000;
|
|
34
|
+
|
|
35
|
+
function configuredExpiryMs() {
|
|
36
|
+
const value = Number(process.env.MIXDOG_HELD_REQUEST_EXPIRY_MS);
|
|
37
|
+
return Number.isFinite(value) && value > 0 ? value : HELD_REQUEST_EXPIRY_MS;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const runs = new Map(); // sessionId → { kind, agentId, name }
|
|
41
|
+
|
|
42
|
+
/** Mark `sessionId` as an unattended schedule/webhook run of `agentId`. */
|
|
43
|
+
export function markUnattendedRun(sessionId, { kind, agentId, name = '' }) {
|
|
44
|
+
if (!sessionId) return () => {};
|
|
45
|
+
const run = {
|
|
46
|
+
kind: kind === 'webhook' ? 'webhook' : 'schedule',
|
|
47
|
+
agentId: agentId || MIXY_AGENT_ID,
|
|
48
|
+
name: String(name),
|
|
49
|
+
};
|
|
50
|
+
runs.set(sessionId, run);
|
|
51
|
+
recordRunOwner(sessionId, run.agentId);
|
|
52
|
+
return () => {
|
|
53
|
+
if (runs.get(sessionId) === run) runs.delete(sessionId);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The unattended run `sessionId` belongs to, or null for an attended session. */
|
|
58
|
+
export function unattendedRunOf(sessionId) {
|
|
59
|
+
const run = runs.get(sessionId);
|
|
60
|
+
if (run) return run;
|
|
61
|
+
const member = roomMemberOfSession(sessionId);
|
|
62
|
+
return member ? { kind: 'room', agentId: member.agentId, name: member.roomId } : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A schedule or webhook turn has nobody to hand a step to: a takeover there
|
|
67
|
+
* (an expired sign-in, 2FA, a CAPTCHA, a confirmation) ends the turn at once
|
|
68
|
+
* instead of waiting, and the routine turn (routine-home-turn.mjs) fails with
|
|
69
|
+
* routineHumanNeededText. `outcome` is login_required, human_action_required
|
|
70
|
+
* or sensitive_confirmation_required. Rooms keep their own UI. Resolves false
|
|
71
|
+
* for a session that is not such a run.
|
|
72
|
+
*/
|
|
73
|
+
export async function failRunForHuman(sessionId, outcome, site, abortTurn = defaultAbortTurn) {
|
|
74
|
+
const run = runs.get(sessionId);
|
|
75
|
+
if (!run) return false;
|
|
76
|
+
run.humanNeeded = { outcome, site: String(site || '') };
|
|
77
|
+
await abortTurn(sessionId);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function defaultAbortTurn(sessionId) {
|
|
82
|
+
const { abortSessionTurn } = await import('../runtime/agent/orchestrator/session/manager/session-close.mjs');
|
|
83
|
+
abortSessionTurn(sessionId, 'login-required');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Push name of an agent; the representative's display name for the Lead. */
|
|
87
|
+
function defaultAgentName(agentId) {
|
|
88
|
+
return agentId === MIXY_AGENT_ID ? mixyDisplayName() : agentId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The held-request registry. `deliver(agentId, held)` posts a row into the
|
|
93
|
+
* home thread; `push(event)` sends the sanitized phone push; `record(gate,
|
|
94
|
+
* approval)` writes the audit line.
|
|
95
|
+
*/
|
|
96
|
+
export function createHeldRequests({
|
|
97
|
+
deliver,
|
|
98
|
+
push = notifyHeldRequest,
|
|
99
|
+
record = recordHumanDecision,
|
|
100
|
+
agentName = defaultAgentName,
|
|
101
|
+
expiryMs = configuredExpiryMs,
|
|
102
|
+
runOf = unattendedRunOf,
|
|
103
|
+
now = Date.now,
|
|
104
|
+
log = (line) => process.stderr.write(`mixdog held-requests: ${line}\n`),
|
|
105
|
+
}) {
|
|
106
|
+
const pending = new Map();
|
|
107
|
+
const post = (agentId, held) =>
|
|
108
|
+
Promise.resolve()
|
|
109
|
+
.then(() => deliver(agentId, held))
|
|
110
|
+
.catch((error) => log(`held request ${held.id} could not be delivered: ${error?.message || error}`));
|
|
111
|
+
|
|
112
|
+
function settle(id, approval, status) {
|
|
113
|
+
const entry = pending.get(id);
|
|
114
|
+
if (!entry) return false;
|
|
115
|
+
pending.delete(id);
|
|
116
|
+
clearTimeout(entry.timer);
|
|
117
|
+
entry.release();
|
|
118
|
+
void post(entry.agentId, { ...entry.held, status, ...(approval.device ? { device: approval.device } : {}) });
|
|
119
|
+
void Promise.resolve()
|
|
120
|
+
.then(() => record(entry.gate, approval))
|
|
121
|
+
.catch((error) => log(`held request ${id} audit failed: ${error?.message || error}`));
|
|
122
|
+
entry.resolve(approval);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Hold `request` (the approval hook's payload) until answered or expired. */
|
|
127
|
+
function hold(request = {}, { signal } = {}) {
|
|
128
|
+
const run = runOf(request.sessionId) || { kind: 'schedule', agentId: MIXY_AGENT_ID, name: '' };
|
|
129
|
+
const id = `held_${randomUUID()}`;
|
|
130
|
+
const ms = typeof expiryMs === 'function' ? expiryMs() : expiryMs;
|
|
131
|
+
const held = normalizeHeldRequest({
|
|
132
|
+
id,
|
|
133
|
+
status: 'pending',
|
|
134
|
+
agentId: run.agentId,
|
|
135
|
+
kind: request.kind || 'approval',
|
|
136
|
+
tool: request.name,
|
|
137
|
+
target: request.outbound?.target || '',
|
|
138
|
+
reason: request.reason || '',
|
|
139
|
+
...(request.kind === 'outbound'
|
|
140
|
+
? {
|
|
141
|
+
payload: request.outbound?.payload || '',
|
|
142
|
+
action: request.outbound?.category,
|
|
143
|
+
account: request.outbound?.account,
|
|
144
|
+
editable: request.outbound?.editable,
|
|
145
|
+
details: request.outbound?.details,
|
|
146
|
+
}
|
|
147
|
+
: {}),
|
|
148
|
+
remember: request.remember || ['once'],
|
|
149
|
+
run: { kind: run.kind, name: run.name, sessionId: request.sessionId },
|
|
150
|
+
expiresAt: now() + ms,
|
|
151
|
+
});
|
|
152
|
+
const shape = { kind: held.kind, outbound: request.outbound, remember: request.remember || ['once'] };
|
|
153
|
+
const gate = {
|
|
154
|
+
agentId: run.agentId,
|
|
155
|
+
kind: held.kind,
|
|
156
|
+
...(held.action ? { action: held.action } : {}),
|
|
157
|
+
tool: held.tool,
|
|
158
|
+
target: held.target,
|
|
159
|
+
};
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
const onAbort = () => settle(id, { ...normalizeHumanResponse(shape, false), reason: 'run cancelled' }, 'denied');
|
|
162
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
163
|
+
const entry = {
|
|
164
|
+
held,
|
|
165
|
+
shape,
|
|
166
|
+
gate,
|
|
167
|
+
agentId: run.agentId,
|
|
168
|
+
resolve,
|
|
169
|
+
release: () => signal?.removeEventListener?.('abort', onAbort),
|
|
170
|
+
timer: setTimeout(
|
|
171
|
+
() =>
|
|
172
|
+
settle(
|
|
173
|
+
id,
|
|
174
|
+
{ ...normalizeHumanResponse(shape, false), reason: 'held request expired', timedOut: true },
|
|
175
|
+
'expired'
|
|
176
|
+
),
|
|
177
|
+
ms
|
|
178
|
+
),
|
|
179
|
+
};
|
|
180
|
+
entry.timer.unref?.();
|
|
181
|
+
pending.set(id, entry);
|
|
182
|
+
void post(run.agentId, held);
|
|
183
|
+
try {
|
|
184
|
+
push({
|
|
185
|
+
sessionId: agentHomeSessionId(run.agentId) || agentHomeSessionId(MIXY_AGENT_ID),
|
|
186
|
+
agentId: run.agentId,
|
|
187
|
+
agentName: agentName(run.agentId),
|
|
188
|
+
requestId: id,
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log(`held request ${id} push failed: ${error?.message || error}`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Answer held request `id`; false when it is not pending (already settled). */
|
|
197
|
+
function resolve(id, decision = {}) {
|
|
198
|
+
const entry = pending.get(id);
|
|
199
|
+
if (!entry) return false;
|
|
200
|
+
const approved = decision === true || decision?.approved === true;
|
|
201
|
+
const approval = {
|
|
202
|
+
...normalizeHumanResponse(entry.shape, approved, decision),
|
|
203
|
+
reason: String(decision?.reason || (approved ? 'approved by user' : 'denied by user')),
|
|
204
|
+
...(typeof decision?.device === 'string' && decision.device ? { device: decision.device } : {}),
|
|
205
|
+
};
|
|
206
|
+
return settle(id, approval, approved ? 'approved' : 'denied');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { hold, resolve, pending: () => [...pending.values()].map((entry) => entry.held) };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let processHeld = null;
|
|
213
|
+
async function defaultDeliver(agentId, held) {
|
|
214
|
+
const { deliverHeldRequest } = await import('./agent-home-delivery.mjs');
|
|
215
|
+
return deliverHeldRequest(agentId, held);
|
|
216
|
+
}
|
|
217
|
+
function heldRequests() {
|
|
218
|
+
processHeld ??= createHeldRequests({ deliver: defaultDeliver });
|
|
219
|
+
return processHeld;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Hold one request for its session's agent (rooms call this from their own hook). */
|
|
223
|
+
export function holdApprovalRequest(request, options) {
|
|
224
|
+
return heldRequests().hold(request, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setUnattendedApprovalPort({ runOf: unattendedRunOf, hold: holdApprovalRequest, humanNeeded: failRunForHuman });
|
|
228
|
+
registerHeldRequestResolver((id, decision) => heldRequests().resolve(id, decision));
|
|
@@ -1,11 +1,123 @@
|
|
|
1
1
|
// ask_user: put 1-4 multiple-choice questions to the user through the session's
|
|
2
2
|
// tool-approval hook (kind: 'question') and report the answers to the model.
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import {
|
|
5
|
+
validateAskUserHandoff,
|
|
4
6
|
validateAskUserQuestions,
|
|
5
7
|
ASK_USER_MAX_TEXT,
|
|
6
8
|
} from '../../runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs';
|
|
7
9
|
import { createCallerContextResolvers } from './caller-context.mjs';
|
|
8
10
|
import { runAbortable } from '../../runtime/shared/abort-race.mjs';
|
|
11
|
+
import { SECRET_REQUEST_TIMEOUT_MS } from '../../runtime/agent/orchestrator/session/human-gate/requests.mjs';
|
|
12
|
+
import {
|
|
13
|
+
agentIdForSession,
|
|
14
|
+
recordHumanDecision,
|
|
15
|
+
unattendedApprovalPort,
|
|
16
|
+
} from '../../runtime/agent/orchestrator/session/human-gate/gate.mjs';
|
|
17
|
+
import { browserSecretInput } from '../../runtime/browser-bridge/client.mjs';
|
|
18
|
+
import { computerSecretInput } from '../../runtime/computer-bridge/client.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A secret or takeover handoff. Secret: the card collects the value on the
|
|
22
|
+
* desktop, which holds it for `requestId` and fills it into the target field
|
|
23
|
+
* (isolated-world fill for the browser, one-shot typed input for computer
|
|
24
|
+
* use); here only the verdict travels, and the result is exactly 'provided',
|
|
25
|
+
* 'skipped' or 'timeout'. Takeover: the run waits until the user says done
|
|
26
|
+
* (computer and browser actions stay blocked meanwhile, desktop
|
|
27
|
+
* main/takeover-gate.ts); the result is 'done' or 'denied'.
|
|
28
|
+
*/
|
|
29
|
+
export async function runAskUserHandoff(handoff, { hook, sessionId, toolCallId, signal, inputs = {} }) {
|
|
30
|
+
const secretInput = {
|
|
31
|
+
browser: inputs.browser || browserSecretInput,
|
|
32
|
+
computer: inputs.computer || computerSecretInput,
|
|
33
|
+
};
|
|
34
|
+
const gate = {
|
|
35
|
+
agentId: agentIdForSession(sessionId),
|
|
36
|
+
kind: handoff.kind,
|
|
37
|
+
tool: `ask_user:${handoff.surface}`,
|
|
38
|
+
target: handoff.target || handoff.field || '',
|
|
39
|
+
};
|
|
40
|
+
// A schedule or webhook run has nobody to take over: the step ends it now,
|
|
41
|
+
// reported by what it needs (a sign-in, a step, a confirmation), never a waiting card.
|
|
42
|
+
const outcome = HANDOFF_OUTCOMES[handoff.need] || HANDOFF_OUTCOMES.action;
|
|
43
|
+
if (
|
|
44
|
+
handoff.kind === 'takeover' &&
|
|
45
|
+
(await unattendedApprovalPort()?.humanNeeded?.(sessionId, outcome, handoffSite(handoff)))
|
|
46
|
+
) {
|
|
47
|
+
return UNATTENDED_TAKEOVER;
|
|
48
|
+
}
|
|
49
|
+
if (typeof hook !== 'function') {
|
|
50
|
+
// Nobody can take over here: blocked, never "proceed".
|
|
51
|
+
if (handoff.kind === 'takeover') return NO_TAKEOVER;
|
|
52
|
+
await recordHumanDecision(gate, { response: 'skipped' });
|
|
53
|
+
return 'skipped';
|
|
54
|
+
}
|
|
55
|
+
if (handoff.kind === 'secret') {
|
|
56
|
+
const requestId = `secret_${randomUUID()}`;
|
|
57
|
+
const result = await runAbortable(signal, () =>
|
|
58
|
+
hook({
|
|
59
|
+
name: 'ask_user',
|
|
60
|
+
kind: 'secret',
|
|
61
|
+
secret: { requestId, surface: handoff.surface, target: handoff.target, field: handoff.field },
|
|
62
|
+
reason: '',
|
|
63
|
+
timeoutMs: SECRET_REQUEST_TIMEOUT_MS,
|
|
64
|
+
sessionId,
|
|
65
|
+
toolCallId,
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
let verdict = 'skipped';
|
|
69
|
+
if (result?.timedOut === true) verdict = 'timeout';
|
|
70
|
+
else if (result?.approved === true && result.response === 'provide-secret') {
|
|
71
|
+
verdict = await secretInput[handoff.surface](sessionId, requestId, { signal });
|
|
72
|
+
}
|
|
73
|
+
await recordHumanDecision(gate, { response: verdict, device: result?.device });
|
|
74
|
+
return verdict;
|
|
75
|
+
}
|
|
76
|
+
const result = await runAbortable(signal, () =>
|
|
77
|
+
hook({
|
|
78
|
+
name: 'ask_user',
|
|
79
|
+
kind: 'takeover',
|
|
80
|
+
takeover: {
|
|
81
|
+
surface: handoff.surface,
|
|
82
|
+
instructions: handoff.instructions,
|
|
83
|
+
target: handoff.target,
|
|
84
|
+
...handoff.identity,
|
|
85
|
+
// A login takeover: the desktop opens its sign-in window on this page.
|
|
86
|
+
...(handoff.login ? { purpose: 'login', url: handoff.login.url } : {}),
|
|
87
|
+
},
|
|
88
|
+
args: { surface: handoff.surface, instructions: handoff.instructions },
|
|
89
|
+
reason: '',
|
|
90
|
+
timeoutMs: null,
|
|
91
|
+
sessionId,
|
|
92
|
+
toolCallId,
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
const answer = result?.approved === true ? 'done' : 'denied';
|
|
96
|
+
await recordHumanDecision(gate, { response: answer, device: result?.device });
|
|
97
|
+
return answer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The unattended outcome of a takeover by what it needs (routine-result.mjs). */
|
|
101
|
+
const HANDOFF_OUTCOMES = Object.freeze({
|
|
102
|
+
sign_in: 'login_required',
|
|
103
|
+
action: 'human_action_required',
|
|
104
|
+
confirmation: 'sensitive_confirmation_required',
|
|
105
|
+
});
|
|
106
|
+
const UNATTENDED_TAKEOVER =
|
|
107
|
+
'Nobody is present in this routine run, so it ends here and the user is told what needs them. Stop; do not retry or work around this step.';
|
|
108
|
+
const NO_TAKEOVER =
|
|
109
|
+
'Blocked: nobody can take over in this session, so this step was not done. Stop and tell the user what needs them; do not work around it.';
|
|
110
|
+
|
|
111
|
+
/** The site a takeover is about: the first host its target (or instructions)
|
|
112
|
+
* names, else the target as the model wrote it. */
|
|
113
|
+
export function handoffSite(handoff) {
|
|
114
|
+
if (handoff.login) return new URL(handoff.login.url).hostname;
|
|
115
|
+
for (const text of [handoff.target, handoff.instructions]) {
|
|
116
|
+
const host = /\b((?:[a-z0-9-]+\.)+[a-z]{2,})\b/i.exec(String(text || ''))?.[1];
|
|
117
|
+
if (host) return host.toLowerCase();
|
|
118
|
+
}
|
|
119
|
+
return String(handoff.target || '').trim();
|
|
120
|
+
}
|
|
9
121
|
|
|
10
122
|
const NO_USER =
|
|
11
123
|
'No user is available to answer in this session. Proceed with your best judgment and state the choices you made.';
|
|
@@ -63,6 +175,15 @@ export function createAskUserToolHandlers({ rt }) {
|
|
|
63
175
|
const { sessionIdFor, signalFor } = createCallerContextResolvers(rt);
|
|
64
176
|
return {
|
|
65
177
|
ask_user: async (args, { callerCtx }) => {
|
|
178
|
+
const handoff = validateAskUserHandoff(args);
|
|
179
|
+
if (handoff) {
|
|
180
|
+
return runAskUserHandoff(handoff, {
|
|
181
|
+
hook: callerCtx?.toolApprovalHook,
|
|
182
|
+
sessionId: sessionIdFor(callerCtx),
|
|
183
|
+
toolCallId: callerCtx?.toolCallId || null,
|
|
184
|
+
signal: signalFor(callerCtx),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
66
187
|
const questions = validateAskUserQuestions(args);
|
|
67
188
|
const hook = callerCtx?.toolApprovalHook;
|
|
68
189
|
if (typeof hook !== 'function') return NO_USER;
|
|
@@ -46,6 +46,10 @@ export function createFeatureToolHandlers({ rt, setupTool, officeToolsEnabled, m
|
|
|
46
46
|
signal: signalFor(callerCtx),
|
|
47
47
|
});
|
|
48
48
|
},
|
|
49
|
-
setup: async (args, { callerCtx }) =>
|
|
49
|
+
setup: async (args, { callerCtx }) =>
|
|
50
|
+
await setupTool.execute(args || {}, {
|
|
51
|
+
signal: signalFor(callerCtx),
|
|
52
|
+
callerSessionId: callerCtx?.callerSessionId || null,
|
|
53
|
+
}),
|
|
50
54
|
};
|
|
51
55
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// Tools that read rather than act: web search/fetch, memory, the code graph,
|
|
2
2
|
// the deferred tool catalog, and skill bodies.
|
|
3
3
|
import {
|
|
4
|
+
deferredCatalogUnion,
|
|
4
5
|
refreshDeferredMcpToolCatalog,
|
|
5
6
|
renderToolSearch,
|
|
6
7
|
} from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
|
|
8
|
+
import { parseToolSelection } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs';
|
|
7
9
|
import { resolveDeferredSelectMode } from '../../runtime/agent/orchestrator/session/loop/deferred-call-through.mjs';
|
|
8
10
|
import { dispatchWebSearchRuntimeTool, memoryToolArgsForCaller } from '../runtime-tool-routing.mjs';
|
|
9
11
|
import { createCallerContextResolvers } from './caller-context.mjs';
|
|
12
|
+
import { memoryCallerScope } from '../services/memory-access.mjs';
|
|
10
13
|
|
|
11
14
|
export function createKnowledgeToolHandlers({
|
|
12
15
|
rt,
|
|
@@ -17,6 +20,7 @@ export function createKnowledgeToolHandlers({
|
|
|
17
20
|
runNativeWebSearch,
|
|
18
21
|
activeToolSurface,
|
|
19
22
|
mcpStatus,
|
|
23
|
+
awaitMcpConnect,
|
|
20
24
|
skillToolContent,
|
|
21
25
|
}) {
|
|
22
26
|
const { signalFor } = createCallerContextResolvers(rt);
|
|
@@ -29,10 +33,23 @@ export function createKnowledgeToolHandlers({
|
|
|
29
33
|
runNativeWebSearch,
|
|
30
34
|
});
|
|
31
35
|
|
|
36
|
+
// The calling session's memory scope always replaces any model-supplied
|
|
37
|
+
// one: the memory runtime authorizes every pool read and write by it. A
|
|
38
|
+
// caller named but not resolvable here counts as an unattended run.
|
|
39
|
+
const memoryCallerSession = (callerCtx) => {
|
|
40
|
+
if (callerCtx?.callerSession) return callerCtx.callerSession;
|
|
41
|
+
const id = callerCtx?.callerSessionId;
|
|
42
|
+
return !id || id === rt.session?.id ? rt.session || null : null;
|
|
43
|
+
};
|
|
32
44
|
const memory = async (args, { name, callerCtx, callerCwd }) => {
|
|
33
45
|
const memoryMod = await getMemoryModule();
|
|
34
46
|
if (!memoryMod?.handleToolCall) throw new Error('memory runtime is not available');
|
|
35
|
-
|
|
47
|
+
const memoryCaller = memoryCallerScope(memoryCallerSession(callerCtx));
|
|
48
|
+
return await memoryMod.handleToolCall(
|
|
49
|
+
name,
|
|
50
|
+
{ ...memoryToolArgsForCaller(args, callerCwd), memory_caller: memoryCaller },
|
|
51
|
+
signalFor(callerCtx)
|
|
52
|
+
);
|
|
36
53
|
};
|
|
37
54
|
|
|
38
55
|
// Catalog tools answer for the calling session's own scope, bridge or
|
|
@@ -41,9 +58,21 @@ export function createKnowledgeToolHandlers({
|
|
|
41
58
|
const scopeSession = (callerCtx) => callerCtx?.callerSession || activeToolSurface();
|
|
42
59
|
const scopeMode = (session) => session?.deferredSurfaceMode || resolveDeferredSelectMode(session) || rt.mode;
|
|
43
60
|
|
|
44
|
-
const
|
|
61
|
+
const catalogLacks = (session, names) => {
|
|
62
|
+
const known = new Set(deferredCatalogUnion(session).map((tool) => String(tool?.name || '').toLowerCase()));
|
|
63
|
+
return parseToolSelection(names).some((name) => !known.has(String(name).toLowerCase()));
|
|
64
|
+
};
|
|
65
|
+
// A name the catalog does not hold yet may belong to a server still in its
|
|
66
|
+
// handshake (a fresh session's first turn, or the reconnect after a
|
|
67
|
+
// sign-in): wait for that connect before reporting it missing. Returns at
|
|
68
|
+
// once when no connect is in flight.
|
|
69
|
+
const toolSearch = async (args, { callerCtx } = {}) => {
|
|
45
70
|
const session = scopeSession(callerCtx);
|
|
46
71
|
refreshDeferredMcpToolCatalog(session, rt.config);
|
|
72
|
+
if (awaitMcpConnect && catalogLacks(session, args?.names)) {
|
|
73
|
+
await awaitMcpConnect();
|
|
74
|
+
refreshDeferredMcpToolCatalog(session, rt.config);
|
|
75
|
+
}
|
|
47
76
|
return renderToolSearch(args, session, scopeMode(session), { mcpStatus });
|
|
48
77
|
};
|
|
49
78
|
|
|
@@ -5,9 +5,33 @@ import { resolve } from 'node:path';
|
|
|
5
5
|
import { listProjects } from '../../runtime/shared/projects.mjs';
|
|
6
6
|
import { applySessionSidebarChange } from '../../runtime/shared/session-sidebar-host.mjs';
|
|
7
7
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
8
|
+
import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
|
|
9
|
+
import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
10
|
+
import { scheduleCallerScope } from '../services/schedule-access.mjs';
|
|
11
|
+
import { resolveHandoffAttachments, sessionRoots } from '../handoff-attachments.mjs';
|
|
8
12
|
|
|
9
13
|
const USER_TITLE_ERROR = 'session: the user already titled this session; the title was not changed';
|
|
10
14
|
|
|
15
|
+
/** The agent an attended agent conversation (an agent's home thread) acts as;
|
|
16
|
+
* '' for every other caller: delegated workers, coding sessions (task
|
|
17
|
+
* sessions included) and unattended schedule/webhook runs. */
|
|
18
|
+
function attendedAgentOf(session) {
|
|
19
|
+
const { mode, agentId } = resolveSessionMode({ session });
|
|
20
|
+
const { kind } = scheduleCallerScope(session);
|
|
21
|
+
return mode === AGENT_MODE && (kind === 'human' || kind === 'agent') ? agentId : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The caller's `attachments` as stored prompt parts, resolved against its own
|
|
25
|
+
* conversation and folders (handoff-attachments.mjs). */
|
|
26
|
+
function callerAttachmentParts(rt, args, callerCwd, surface) {
|
|
27
|
+
return resolveHandoffAttachments(args?.attachments, {
|
|
28
|
+
messages: rt.session?.messages,
|
|
29
|
+
roots: sessionRoots(rt.session, callerCwd),
|
|
30
|
+
cwd: callerCwd,
|
|
31
|
+
surface,
|
|
32
|
+
}).parts;
|
|
33
|
+
}
|
|
34
|
+
|
|
11
35
|
export function createWorkspaceToolHandlers({
|
|
12
36
|
rt,
|
|
13
37
|
goalRuntime,
|
|
@@ -65,12 +89,37 @@ export function createWorkspaceToolHandlers({
|
|
|
65
89
|
const session = async (args, { callerCtx, callerCwd }) => {
|
|
66
90
|
const action = clean(args?.action || (args?.path ? 'set-project' : 'status')).toLowerCase();
|
|
67
91
|
const target = clean(args?.session_id);
|
|
68
|
-
if (['list', 'read', 'send', 'cancel', 'delete'].includes(action) || (action === 'status' && target)) {
|
|
92
|
+
if (['list', 'read', 'send', 'cancel', 'delete', 'create'].includes(action) || (action === 'status' && target)) {
|
|
69
93
|
const callerSessionId = assertLead(action, callerCtx);
|
|
94
|
+
const callerAgentId = attendedAgentOf(rt.session);
|
|
95
|
+
if (action === 'create' && !callerAgentId) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'session: create belongs to a bot conversation; delegated workers, agent task sessions and schedule/webhook runs cannot create agent task sessions'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
// A task session reaches its agent only through its reported result.
|
|
101
|
+
const origin = rt.session?.taskOrigin;
|
|
102
|
+
if (
|
|
103
|
+
action === 'send' &&
|
|
104
|
+
origin &&
|
|
105
|
+
(target === origin.sessionId || agentIdOfHomeSession(target) === origin.agentId)
|
|
106
|
+
) {
|
|
107
|
+
throw new Error('session: a task session reports to its agent only through its result');
|
|
108
|
+
}
|
|
70
109
|
if (typeof executeSessionControl !== 'function') {
|
|
71
110
|
throw new Error('session: cross-session control is unavailable in this runtime');
|
|
72
111
|
}
|
|
73
|
-
|
|
112
|
+
const { attachments, ...rest } = args || {};
|
|
113
|
+
if (attachments !== undefined && action !== 'send' && action !== 'create') {
|
|
114
|
+
throw new Error(`session: attachments apply to send and create, not action=${action}`);
|
|
115
|
+
}
|
|
116
|
+
const attachmentParts = callerAttachmentParts(rt, args, callerCwd, 'session');
|
|
117
|
+
return JSON.stringify(
|
|
118
|
+
await executeSessionControl(
|
|
119
|
+
{ ...rest, action },
|
|
120
|
+
{ callerSessionId, callerAgentId, callerCwd, ...(attachmentParts.length ? { attachmentParts } : {}) }
|
|
121
|
+
)
|
|
122
|
+
);
|
|
74
123
|
}
|
|
75
124
|
if (target && target !== assertLead(action, callerCtx)) {
|
|
76
125
|
throw new Error(`session: action=${action} only applies to the current session`);
|
|
@@ -192,7 +241,11 @@ export function createWorkspaceToolHandlers({
|
|
|
192
241
|
}),
|
|
193
242
|
agent: async (args, { callerCtx, callerCwd }) => {
|
|
194
243
|
const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
|
|
195
|
-
|
|
244
|
+
// `attachmentParts` is never the model's to set: only the resolved
|
|
245
|
+
// `attachments` reach the agent's turn.
|
|
246
|
+
const { attachments, attachmentParts: _ignored, ...rest } = args || {};
|
|
247
|
+
const attachmentParts = callerAttachmentParts(rt, { attachments }, callerCwd, 'agent');
|
|
248
|
+
return await agentTool.execute(attachmentParts.length ? { ...rest, attachmentParts } : rest, {
|
|
196
249
|
callerCwd,
|
|
197
250
|
invocationSource: 'model-tool',
|
|
198
251
|
callerSessionId,
|
|
@@ -30,6 +30,7 @@ export function createInternalToolExecutor({
|
|
|
30
30
|
runNativeWebSearch,
|
|
31
31
|
activeToolSurface,
|
|
32
32
|
mcpStatus,
|
|
33
|
+
awaitMcpConnect,
|
|
33
34
|
skillToolContent,
|
|
34
35
|
getWorkspaceApi,
|
|
35
36
|
getSessionManager,
|
|
@@ -49,6 +50,7 @@ export function createInternalToolExecutor({
|
|
|
49
50
|
runNativeWebSearch,
|
|
50
51
|
activeToolSurface,
|
|
51
52
|
mcpStatus,
|
|
53
|
+
awaitMcpConnect,
|
|
52
54
|
skillToolContent,
|
|
53
55
|
}),
|
|
54
56
|
...createWorkspaceToolHandlers({
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* catalog: durable visibility filter, lead-shape heuristics and preview rows.
|
|
4
4
|
*/
|
|
5
5
|
import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
|
|
6
|
+
import { normalizeRoutineProvenance } from '../../runtime/agent/orchestrator/session/routine-result.mjs';
|
|
7
|
+
import { sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
|
|
6
8
|
import {
|
|
7
9
|
isAgentOnlySession,
|
|
8
10
|
isRootLeadSession,
|
|
@@ -70,9 +72,12 @@ export function leadSessionRow(s, working = false) {
|
|
|
70
72
|
if (!agentSession && !isLeadShaped(s, owner, sourceType)) return null;
|
|
71
73
|
const { preview, messageCount, realTurnCount } = previewOf(s);
|
|
72
74
|
if (!preview && messageCount === 0) return null;
|
|
75
|
+
const lastMessageRoutine = normalizeRoutineProvenance(s.lastMessageRoutine);
|
|
73
76
|
return {
|
|
74
77
|
id: s.id,
|
|
75
78
|
agent: s.agent || null,
|
|
79
|
+
// 'agent' | 'coding' (agent-mode.mjs): the desktop renders agent-mode chats.
|
|
80
|
+
sessionMode: sessionModeOf(s),
|
|
76
81
|
owner,
|
|
77
82
|
visibility: isAgentOnlySession(s) ? 'agent-only' : 'ordinary',
|
|
78
83
|
ownerSessionId: s.ownerSessionId || s.parentSessionId || null,
|
|
@@ -92,6 +97,10 @@ export function leadSessionRow(s, working = false) {
|
|
|
92
97
|
messageCount,
|
|
93
98
|
title: cleanSessionPreview(s.title || '', 100),
|
|
94
99
|
preview,
|
|
100
|
+
// The latest user or assistant line (an agent contact's row shows it).
|
|
101
|
+
...(s.lastMessagePreview ? { lastMessagePreview: String(s.lastMessagePreview) } : {}),
|
|
102
|
+
// That line's routine provenance: clients label a routine result by it.
|
|
103
|
+
...(lastMessageRoutine ? { lastMessageRoutine } : {}),
|
|
95
104
|
// Working indicator: the in-memory runtime state ALONE is the liveness
|
|
96
105
|
// signal. The persisted lastHeartbeatAt JSON field (refreshed by the final
|
|
97
106
|
// save) must not be folded in — it outlives the turn it describes.
|
|
@@ -60,6 +60,7 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
60
60
|
getStandaloneTools,
|
|
61
61
|
beginRoutePreparation,
|
|
62
62
|
clearRoutePreparation,
|
|
63
|
+
refreshAgentProfile,
|
|
63
64
|
} = deps;
|
|
64
65
|
|
|
65
66
|
function restoreResumedToolSurface(target, targetRoute) {
|
|
@@ -165,6 +166,9 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
165
166
|
// preparation temporarily dropped loaded tools and invalidated the
|
|
166
167
|
// provider-aligned context baseline.
|
|
167
168
|
restoreResumedToolSurface(resumed, resumedRoute);
|
|
169
|
+
// A thread running as an agent whose profile was edited while no runtime
|
|
170
|
+
// held it takes the saved profile now (tool-policy-refresh.mjs).
|
|
171
|
+
await refreshAgentProfile?.({ trigger: 'resume' });
|
|
168
172
|
const finishRoutePreparation = routePreparation(resumed, resumedRoute);
|
|
169
173
|
if (typeof beginRoutePreparation === 'function') {
|
|
170
174
|
beginRoutePreparation(finishRoutePreparation);
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* lifecycle groups (teardown, session switching, inheritance).
|
|
4
4
|
*/
|
|
5
5
|
import { hasUserConversationMessage } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
6
|
+
import { isAgentHomeSessionId } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
7
|
+
import { isRoomThreadSessionId } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
6
8
|
|
|
7
9
|
export { SESSION_ID_PATTERN } from '../../runtime/agent/orchestrator/runtime-core/session-id.mjs';
|
|
8
10
|
|
|
@@ -19,7 +21,11 @@ export function isScratchConversation(messages, liveTurnMessages) {
|
|
|
19
21
|
);
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/** An agent's home thread is never scratch: a blank one is tombstoned on no
|
|
25
|
+
* close, so its fixed id stays open for the next visit. Neither is a group
|
|
26
|
+
* room's log or member session (agent-room.mjs), whose ids are fixed too. */
|
|
22
27
|
export function isScratchSession(session) {
|
|
28
|
+
if (isAgentHomeSessionId(session?.id) || isRoomThreadSessionId(session?.id)) return false;
|
|
23
29
|
return isScratchConversation(session?.messages, session?.liveTurnMessages);
|
|
24
30
|
}
|
|
25
31
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// servers plus the servers registered plugins declare (resolved live, with
|
|
3
3
|
// explicit owner metadata), per-server config reads, and the live/failed rollup.
|
|
4
4
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
5
|
+
import { connectorOfServer, gmailAppPasswordAccount } from '../../runtime/shared/connector-catalog.mjs';
|
|
5
6
|
import { envFlag } from '../../runtime/shared/env.mjs';
|
|
6
7
|
import { resolveMcpServerCatalog } from './plugin-servers.mjs';
|
|
7
8
|
|
|
@@ -142,9 +143,16 @@ export function createMcpServerCatalog({
|
|
|
142
143
|
source: sources[name] || 'config',
|
|
143
144
|
};
|
|
144
145
|
if (authRequired) row.authRequired = true;
|
|
146
|
+
// The catalog service this server connects (connector-catalog.mjs):
|
|
147
|
+
// its marker.
|
|
148
|
+
const connector = entry.owner ? '' : connectorOfServer(cfg);
|
|
149
|
+
if (connector) row.connector = connector;
|
|
145
150
|
const oauth = mcpClient.getMcpServerOAuthState?.(cfg);
|
|
146
151
|
// Why the last sign-in did not complete (already credential-scrubbed).
|
|
147
152
|
if (oauth) row.oauth = authRequired && fail?.msg ? { ...oauth, lastError: fail.msg } : oauth;
|
|
153
|
+
// Gmail connected with an app password: the account it runs as.
|
|
154
|
+
const appPasswordAccount = gmailAppPasswordAccount(cfg);
|
|
155
|
+
if (appPasswordAccount) row.appPassword = { account: appPasswordAccount };
|
|
148
156
|
if (entry.owner) {
|
|
149
157
|
row.owner = entry.owner;
|
|
150
158
|
row.pluginEnabled = entry.pluginEnabled;
|
|
@@ -70,6 +70,9 @@ export function createMcpServerInput({ mcpClient, getCurrentCwd }) {
|
|
|
70
70
|
if (bearerTokenEnvVar) config.bearer_token_env_var = bearerTokenEnvVar;
|
|
71
71
|
const envHeaders = coerceStringRecord(input.env_http_headers || input.envHttpHeaders);
|
|
72
72
|
if (envHeaders) config.env_http_headers = envHeaders;
|
|
73
|
+
// The user's own OAuth client (앱 번호); its secret never lands here.
|
|
74
|
+
const oauthClientId = clean(input.oauthClientId);
|
|
75
|
+
if (oauthClientId) config.oauthClientId = oauthClientId;
|
|
73
76
|
return config;
|
|
74
77
|
};
|
|
75
78
|
const url = clean(input.url);
|