mixdog 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +49 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +51 -10
- package/src/standalone/daemon.mjs +28 -10
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized Image API adapter for the `openrouter` and `nano-gpt` lanes.
|
|
3
|
+
*
|
|
4
|
+
* Both gateways document the same contract: POST {base}/images with `model`,
|
|
5
|
+
* `prompt`, the model's advertised enum parameters and `input_references`
|
|
6
|
+
* (image_url parts; data URLs accepted), answering `data[].b64_json` (NanoGPT
|
|
7
|
+
* may answer a signed `url` instead) plus `usage`.
|
|
8
|
+
* https://openrouter.ai/docs/guides/overview/multimodal/image-generation
|
|
9
|
+
* https://docs.nano-gpt.com/api-reference/image-generation
|
|
10
|
+
*/
|
|
11
|
+
import { resolveImageApiAuth } from '../auth.mjs';
|
|
12
|
+
import { timeoutSignal } from '../bounded-signal.mjs';
|
|
13
|
+
import { decodeBase64Media, downloadPublicMedia } from '../download.mjs';
|
|
14
|
+
import { mediaError } from '../lanes.mjs';
|
|
15
|
+
import { imageApiUsage, withReportedUsage } from '../media-usage.mjs';
|
|
16
|
+
import { upstreamError } from '../upstream-error.mjs';
|
|
17
|
+
|
|
18
|
+
const IMAGE_TIMEOUT_MS = 180_000;
|
|
19
|
+
const LABELS = Object.freeze({ openrouter: 'OpenRouter image', 'nano-gpt': 'NanoGPT image' });
|
|
20
|
+
// Request field per Studio option; values come from the model's own enums.
|
|
21
|
+
const PARAMS = Object.freeze({
|
|
22
|
+
aspectRatio: 'aspect_ratio',
|
|
23
|
+
resolution: 'resolution',
|
|
24
|
+
quality: 'quality',
|
|
25
|
+
background: 'background',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function referenceUrl(reference) {
|
|
29
|
+
const data = String(reference?.base64 || '');
|
|
30
|
+
return data.startsWith('data:') ? data : `data:${reference?.mime || 'image/png'};base64,${data}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The stored format comes from the bytes, not a label: only rasters the store keeps. */
|
|
34
|
+
function rasterMime(bytes) {
|
|
35
|
+
if (bytes.length >= 8 && bytes.readUInt32BE(0) === 0x89504e47) return 'image/png';
|
|
36
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return 'image/jpeg';
|
|
37
|
+
if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
|
|
38
|
+
return 'image/webp';
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function generateImage({ lane, model, prompt, options = {}, references = [], signal }) {
|
|
43
|
+
const label = LABELS[lane] || 'Image API';
|
|
44
|
+
const { baseURL, token } = resolveImageApiAuth(lane);
|
|
45
|
+
const body = { model, prompt, n: 1 };
|
|
46
|
+
for (const [option, param] of Object.entries(PARAMS)) {
|
|
47
|
+
const value = String(options?.[option] || '').trim();
|
|
48
|
+
if (value) body[param] = value;
|
|
49
|
+
}
|
|
50
|
+
if (references.length) {
|
|
51
|
+
body.input_references = references.map((reference) => ({
|
|
52
|
+
type: 'image_url',
|
|
53
|
+
image_url: { url: referenceUrl(reference) },
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
let res;
|
|
57
|
+
try {
|
|
58
|
+
res = await fetch(`${baseURL}/images`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
redirect: 'error',
|
|
63
|
+
signal: timeoutSignal(signal, IMAGE_TIMEOUT_MS),
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error?.name === 'TimeoutError') throw mediaError(`${label} timed out`, 'MEDIA_TIMEOUT', 504);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) throw upstreamError(label, res.status, (await res.text().catch(() => '')).slice(0, 400));
|
|
70
|
+
const data = await res.json().catch(() => null);
|
|
71
|
+
const usage = imageApiUsage(data);
|
|
72
|
+
const entry = Array.isArray(data?.data) ? data.data[0] : null;
|
|
73
|
+
let bytes;
|
|
74
|
+
try {
|
|
75
|
+
if (entry?.b64_json) bytes = decodeBase64Media(entry.b64_json, label);
|
|
76
|
+
else if (entry?.url) bytes = await downloadPublicMedia(entry.url, { signal, label });
|
|
77
|
+
else throw mediaError(`${label} returned no image data`, 'MEDIA_EMPTY_RESULT', 502);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw withReportedUsage(error, usage);
|
|
80
|
+
}
|
|
81
|
+
const mime = rasterMime(bytes);
|
|
82
|
+
if (!mime) {
|
|
83
|
+
throw withReportedUsage(
|
|
84
|
+
mediaError(`${label} returned an unsupported image format`, 'MEDIA_UPSTREAM_FAILED', 502),
|
|
85
|
+
usage
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return { bytes, mime, revisedPrompt: entry.revised_prompt || null, usage };
|
|
89
|
+
}
|
|
@@ -5,11 +5,74 @@
|
|
|
5
5
|
* paths) so the Studio never holds a second copy of a session. API-key lanes
|
|
6
6
|
* read the same keychain-backed store the agent providers use.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { readSection } from '../shared/config.mjs';
|
|
9
|
+
import {
|
|
10
|
+
getAgentApiKey,
|
|
11
|
+
hasAgentApiKey,
|
|
12
|
+
hasProviderApiKey,
|
|
13
|
+
resolveProviderApiKey,
|
|
14
|
+
} from '../shared/provider-api-key.mjs';
|
|
9
15
|
|
|
10
16
|
export { hasGrokOAuthCredentials } from '../agent/orchestrator/providers/oauth-credential-probes.mjs';
|
|
11
17
|
|
|
12
18
|
const XAI_BASE_URL = 'https://api.x.ai/v1';
|
|
19
|
+
// Both gateways serve the same normalized Image API (GET /images/models,
|
|
20
|
+
// POST /images) under these documented bases.
|
|
21
|
+
const IMAGE_API_BASE_URL = Object.freeze({
|
|
22
|
+
openrouter: 'https://openrouter.ai/api/v1',
|
|
23
|
+
'nano-gpt': 'https://api.nano-gpt.com/api/v1',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Lanes served by the normalized Image API adapter. */
|
|
27
|
+
export function imageApiLane(laneId) {
|
|
28
|
+
return Object.hasOwn(IMAGE_API_BASE_URL, laneId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function nanoGptHost(baseURL) {
|
|
32
|
+
try {
|
|
33
|
+
const host = new URL(String(baseURL || '')).hostname.toLowerCase();
|
|
34
|
+
return host === 'nano-gpt.com' || host.endsWith('.nano-gpt.com');
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// NanoGPT is not a built-in provider: it is connected as an OpenAI-compatible
|
|
41
|
+
// custom provider, whose key lives under that provider's own id (keychain or
|
|
42
|
+
// its configured environment variable). The first enabled, keyed endpoint on
|
|
43
|
+
// a NanoGPT host is the lane's credential.
|
|
44
|
+
function nanoGptProviders() {
|
|
45
|
+
const providers = readSection('agent')?.providers || {};
|
|
46
|
+
return Object.entries(providers).filter(
|
|
47
|
+
([id, entry]) =>
|
|
48
|
+
id.startsWith('custom-') &&
|
|
49
|
+
entry?.type === 'custom' &&
|
|
50
|
+
entry.enabled !== false &&
|
|
51
|
+
entry.auth !== 'none' &&
|
|
52
|
+
nanoGptHost(entry.baseURL)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Presence-only probe for an Image API lane's key; no secret is read. */
|
|
57
|
+
export function hasImageApiCredentials(laneId) {
|
|
58
|
+
if (laneId === 'openrouter') return hasAgentApiKey('openrouter');
|
|
59
|
+
if (laneId === 'nano-gpt') return nanoGptProviders().some(([id, entry]) => hasProviderApiKey(id, entry));
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Base URL + bearer for an Image API lane (OpenRouter key, or the connected NanoGPT custom provider's key). */
|
|
64
|
+
export function resolveImageApiAuth(laneId) {
|
|
65
|
+
let token = '';
|
|
66
|
+
if (laneId === 'openrouter') token = getAgentApiKey('openrouter') || '';
|
|
67
|
+
else if (laneId === 'nano-gpt') {
|
|
68
|
+
for (const [id, entry] of nanoGptProviders()) {
|
|
69
|
+
token = resolveProviderApiKey(id, entry);
|
|
70
|
+
if (token) break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!imageApiLane(laneId) || !token) throw unauthenticated(`${laneId} API key is not configured`);
|
|
74
|
+
return { baseURL: IMAGE_API_BASE_URL[laneId], token };
|
|
75
|
+
}
|
|
13
76
|
|
|
14
77
|
// Built here rather than with lanes.mjs's mediaError: importing it would close
|
|
15
78
|
// an auth -> lanes -> catalog -> auth module cycle.
|
|
@@ -6,11 +6,26 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
import { makeModelCache } from '../agent/orchestrator/providers/model-cache.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
imageApiLane,
|
|
11
|
+
resolveAntigravityAuth,
|
|
12
|
+
resolveGeminiKey,
|
|
13
|
+
resolveImageApiAuth,
|
|
14
|
+
resolveXaiAuth,
|
|
15
|
+
} from './auth.mjs';
|
|
10
16
|
import { catalogHttpError, staleCatalog } from './catalog-errors.mjs';
|
|
11
17
|
|
|
12
18
|
const TTL_MS = 24 * 60 * 60_000;
|
|
13
19
|
const REQUEST_MS = 10_000;
|
|
20
|
+
// Gateway image catalogs can sit behind a slow edge: api.nano-gpt.com's
|
|
21
|
+
// /models answered only after ~44 s, so the 10 s default would read a slow
|
|
22
|
+
// catalog as an outage. Same budget as custom-provider discovery.
|
|
23
|
+
const GATEWAY_CATALOG_MS = 60_000;
|
|
24
|
+
|
|
25
|
+
/** Per-lane timeout of one catalog request. */
|
|
26
|
+
export function mediaCatalogTimeoutMs(lane) {
|
|
27
|
+
return imageApiLane(lane) ? GATEWAY_CATALOG_MS : REQUEST_MS;
|
|
28
|
+
}
|
|
14
29
|
const natural = new Intl.Collator('en', { numeric: true });
|
|
15
30
|
|
|
16
31
|
function methodsAllow(row, method) {
|
|
@@ -39,8 +54,51 @@ function grokLane(lane) {
|
|
|
39
54
|
return lane === 'xai' || lane === 'grok-oauth';
|
|
40
55
|
}
|
|
41
56
|
|
|
57
|
+
// Image API catalogs (OpenRouter, NanoGPT) describe each model with typed
|
|
58
|
+
// capability descriptors; only enum values become pickable controls.
|
|
59
|
+
const IMAGE_API_CONTROLS = Object.freeze({
|
|
60
|
+
aspect_ratio: 'aspectRatio',
|
|
61
|
+
resolution: 'resolution',
|
|
62
|
+
quality: 'quality',
|
|
63
|
+
background: 'background',
|
|
64
|
+
});
|
|
65
|
+
// The request schema's documented ceiling for `input_references`.
|
|
66
|
+
const IMAGE_API_MAX_REFERENCES = 16;
|
|
67
|
+
|
|
68
|
+
function imageApiOutputsImage(row) {
|
|
69
|
+
const outputs = row.architecture?.output_modalities;
|
|
70
|
+
// Image-to-image-only models (upscalers, editors) need an input image;
|
|
71
|
+
// Studio also runs prompt-only, so they stay out.
|
|
72
|
+
return Array.isArray(outputs) && outputs.includes('image') && row.capabilities?.image_generation !== false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function imageApiReferenceCap(row) {
|
|
76
|
+
const descriptor = row.supported_parameters?.input_references;
|
|
77
|
+
if (descriptor?.type === 'range' && Number.isFinite(Number(descriptor.max))) {
|
|
78
|
+
return Math.max(0, Math.min(IMAGE_API_MAX_REFERENCES, Math.trunc(Number(descriptor.max))));
|
|
79
|
+
}
|
|
80
|
+
const inputs = row.architecture?.input_modalities;
|
|
81
|
+
const acceptsImages = Array.isArray(inputs) && inputs.includes('image') && row.capabilities?.image_to_image !== false;
|
|
82
|
+
return acceptsImages ? IMAGE_API_MAX_REFERENCES : 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function imageApiControls(row) {
|
|
86
|
+
const controls = {};
|
|
87
|
+
for (const [param, control] of Object.entries(IMAGE_API_CONTROLS)) {
|
|
88
|
+
const descriptor = row.supported_parameters?.[param];
|
|
89
|
+
const values =
|
|
90
|
+
descriptor?.type === 'enum' && Array.isArray(descriptor.values)
|
|
91
|
+
? [...new Set(descriptor.values.map((value) => String(value).trim()).filter(Boolean))]
|
|
92
|
+
: [];
|
|
93
|
+
if (values.length) controls[control] = values;
|
|
94
|
+
}
|
|
95
|
+
controls.maxReferences = imageApiReferenceCap(row);
|
|
96
|
+
return controls;
|
|
97
|
+
}
|
|
98
|
+
|
|
42
99
|
function kindFor(lane, row, id) {
|
|
43
100
|
if (row.deprecated === true || row.disabled === true) return null;
|
|
101
|
+
if (imageApiLane(lane)) return imageApiOutputsImage(row) ? 'image' : null;
|
|
44
102
|
if (lane === 'openai-oauth') return imageToolSupported(row, id) ? 'image' : null;
|
|
45
103
|
if (grokLane(lane)) {
|
|
46
104
|
if (/^grok-imagine-image(?:-|$)/.test(id)) return 'image';
|
|
@@ -118,7 +176,9 @@ export function projectMediaModels(lane, rows) {
|
|
|
118
176
|
const kind = kindFor(lane, row, id);
|
|
119
177
|
if (!kind) continue;
|
|
120
178
|
let controls = {};
|
|
121
|
-
if (
|
|
179
|
+
if (imageApiLane(lane)) {
|
|
180
|
+
controls = imageApiControls(row);
|
|
181
|
+
} else if (grokLane(lane) && kind === 'video') {
|
|
122
182
|
controls = { resolution: ['480p', '720p'] };
|
|
123
183
|
} else if (lane === 'gemini' && /^gemini-omni-/.test(id)) {
|
|
124
184
|
controls = { resolution: [], durations: [], maxReferences: 3 };
|
|
@@ -181,6 +241,7 @@ async function providerSource(lane) {
|
|
|
181
241
|
let auth;
|
|
182
242
|
if (lane === 'gemini') auth = { token: resolveGeminiKey() };
|
|
183
243
|
else if (lane === 'antigravity-oauth') auth = await resolveAntigravityAuth();
|
|
244
|
+
else if (imageApiLane(lane)) auth = resolveImageApiAuth(lane);
|
|
184
245
|
else auth = await resolveXaiAuth(lane);
|
|
185
246
|
// API-key and OAuth catalogs must not leak availability across credentials.
|
|
186
247
|
// Persist only a one-way scope hash, never a key or a bearer. OAuth bearers
|
|
@@ -200,7 +261,7 @@ async function providerSource(lane) {
|
|
|
200
261
|
}
|
|
201
262
|
|
|
202
263
|
export async function fetchMediaModelRows({ lane, auth, fetchFn = fetch }) {
|
|
203
|
-
const signal = AbortSignal.timeout(
|
|
264
|
+
const signal = AbortSignal.timeout(mediaCatalogTimeoutMs(lane));
|
|
204
265
|
if (lane === 'gemini') {
|
|
205
266
|
const { fetchGeminiModelPages } = await import('../agent/orchestrator/providers/lib/gemini-model-catalog.mjs');
|
|
206
267
|
return fetchGeminiModelPages(auth.token, async (url, init) => {
|
|
@@ -217,7 +278,8 @@ export async function fetchMediaModelRows({ lane, auth, fetchFn = fetch }) {
|
|
|
217
278
|
return Object.entries(models).map(([id, row]) => ({ ...(row && typeof row === 'object' ? row : {}), id }));
|
|
218
279
|
}
|
|
219
280
|
const { getLlmDispatcher } = await import('../shared/llm/http-agent.mjs');
|
|
220
|
-
|
|
281
|
+
// The Image API lists its generation models apart from the chat catalog.
|
|
282
|
+
const response = await fetchFn(`${auth.baseURL}/${imageApiLane(lane) ? 'images/models' : 'models'}`, {
|
|
221
283
|
headers: { Authorization: `Bearer ${auth.token}` },
|
|
222
284
|
redirect: 'error',
|
|
223
285
|
signal,
|
|
@@ -60,6 +60,10 @@ async function runAdapter({ lane, kind, model, requestModel, prompt, options, re
|
|
|
60
60
|
const adapter = await import('./adapters/codex-image.mjs');
|
|
61
61
|
return await adapter.generateImage({ model: requestModel || model, prompt, options, references, signal });
|
|
62
62
|
}
|
|
63
|
+
if (lane.id === 'openrouter' || lane.id === 'nano-gpt') {
|
|
64
|
+
const adapter = await import('./adapters/image-api.mjs');
|
|
65
|
+
return await adapter.generateImage({ lane: lane.id, model, prompt, options, references, signal });
|
|
66
|
+
}
|
|
63
67
|
if (lane.id === 'antigravity-oauth') {
|
|
64
68
|
const adapter = await import('./adapters/antigravity-image.mjs');
|
|
65
69
|
return await adapter.generateImage({ model, prompt, options, references, signal });
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
hasGrokOAuthCredentials,
|
|
17
17
|
hasOpenAIOAuthCredentials,
|
|
18
18
|
} from '../agent/orchestrator/providers/oauth-credential-probes.mjs';
|
|
19
|
+
import { hasImageApiCredentials, imageApiLane } from './auth.mjs';
|
|
19
20
|
import { loadMediaModels, projectMediaModels } from './catalog.mjs';
|
|
20
21
|
import { catalogDiagnostic } from './catalog-errors.mjs';
|
|
21
22
|
import { MEDIA_KINDS } from './tool-defs.mjs';
|
|
@@ -98,6 +99,20 @@ const LANES = Object.freeze([
|
|
|
98
99
|
controls: GEMINI_IMAGE_CONTROLS,
|
|
99
100
|
}),
|
|
100
101
|
}),
|
|
102
|
+
// Gateway lanes on the normalized Image API: every model advertises its own
|
|
103
|
+
// parameters in the catalog, so the lane declares no controls of its own.
|
|
104
|
+
Object.freeze({
|
|
105
|
+
id: 'openrouter',
|
|
106
|
+
label: 'OpenRouter',
|
|
107
|
+
auth: Object.freeze({ type: 'api-key', provider: 'openrouter' }),
|
|
108
|
+
image: Object.freeze({ controls: Object.freeze({}) }),
|
|
109
|
+
}),
|
|
110
|
+
Object.freeze({
|
|
111
|
+
id: 'nano-gpt',
|
|
112
|
+
label: 'NanoGPT',
|
|
113
|
+
auth: Object.freeze({ type: 'api-key', provider: 'nano-gpt' }),
|
|
114
|
+
image: Object.freeze({ controls: Object.freeze({}) }),
|
|
115
|
+
}),
|
|
101
116
|
]);
|
|
102
117
|
|
|
103
118
|
const LANE_BY_ID = new Map(LANES.map((lane) => [lane.id, lane]));
|
|
@@ -117,6 +132,7 @@ const OAUTH_PROBES = Object.freeze({
|
|
|
117
132
|
|
|
118
133
|
function laneAuthenticated(lane) {
|
|
119
134
|
try {
|
|
135
|
+
if (imageApiLane(lane.id)) return hasImageApiCredentials(lane.id);
|
|
120
136
|
if (lane.auth.type === 'api-key') return hasAgentApiKey(lane.auth.provider);
|
|
121
137
|
return OAUTH_PROBES[lane.auth.provider]?.() === true;
|
|
122
138
|
} catch {
|
|
@@ -143,8 +159,23 @@ function laneKindView(lane, kind, models) {
|
|
|
143
159
|
};
|
|
144
160
|
}
|
|
145
161
|
|
|
162
|
+
const PENDING = Symbol('catalog-pending');
|
|
163
|
+
|
|
164
|
+
/** The catalog rows, or PENDING when they did not arrive within `budgetMs`
|
|
165
|
+
* (0 = wait). The request keeps running; the loader's singleflight lets the
|
|
166
|
+
* next listing join it instead of starting over. */
|
|
167
|
+
async function withinBudget(loading, budgetMs) {
|
|
168
|
+
if (!(budgetMs > 0)) return loading;
|
|
169
|
+
loading.catch(() => {});
|
|
170
|
+
let timer;
|
|
171
|
+
const expired = new Promise((resolve) => {
|
|
172
|
+
timer = setTimeout(resolve, budgetMs, PENDING);
|
|
173
|
+
});
|
|
174
|
+
return Promise.race([loading, expired]).finally(() => clearTimeout(timer));
|
|
175
|
+
}
|
|
176
|
+
|
|
146
177
|
export function createMediaLaneCatalog({ loadModels = loadMediaModels, authenticated = laneAuthenticated } = {}) {
|
|
147
|
-
async function laneView(lane) {
|
|
178
|
+
async function laneView(lane, budgetMs = 0) {
|
|
148
179
|
const signedIn = authenticated(lane);
|
|
149
180
|
const view = {
|
|
150
181
|
id: lane.id,
|
|
@@ -161,7 +192,15 @@ export function createMediaLaneCatalog({ loadModels = loadMediaModels, authentic
|
|
|
161
192
|
};
|
|
162
193
|
if (!signedIn) return view;
|
|
163
194
|
try {
|
|
164
|
-
const rows = await
|
|
195
|
+
const rows = await withinBudget(
|
|
196
|
+
Promise.resolve().then(() => loadModels(lane.id)),
|
|
197
|
+
budgetMs
|
|
198
|
+
);
|
|
199
|
+
if (rows === PENDING) {
|
|
200
|
+
// Still loading, not empty: the caller shows that and asks again.
|
|
201
|
+
view.catalogPending = true;
|
|
202
|
+
return view;
|
|
203
|
+
}
|
|
165
204
|
const models = projectMediaModels(lane.id, rows);
|
|
166
205
|
if (rows.catalogWarning) view.catalogWarning = rows.catalogWarning;
|
|
167
206
|
for (const kind of MEDIA_KINDS) {
|
|
@@ -187,9 +226,11 @@ export function createMediaLaneCatalog({ loadModels = loadMediaModels, authentic
|
|
|
187
226
|
}
|
|
188
227
|
|
|
189
228
|
return {
|
|
190
|
-
/** Live catalog shared by Studio and the model-facing media tool.
|
|
191
|
-
|
|
192
|
-
|
|
229
|
+
/** Live catalog shared by Studio and the model-facing media tool.
|
|
230
|
+
* `budgetMs` bounds the wait per lane: a lane whose catalog is slower
|
|
231
|
+
* answers `catalogPending` instead of holding every other lane. */
|
|
232
|
+
async listMediaLanes({ budgetMs = 0 } = {}) {
|
|
233
|
+
return (await Promise.all(LANES.map((lane) => laneView(lane, budgetMs)))).filter(Boolean);
|
|
193
234
|
},
|
|
194
235
|
async resolveMediaRequest({ lane: laneId, kind, model } = {}) {
|
|
195
236
|
const kindName = String(kind || '').trim();
|
|
@@ -73,6 +73,23 @@ export function xaiUsage(usage) {
|
|
|
73
73
|
return typeof ticks === 'number' && Number.isFinite(ticks) && ticks >= 0 ? { costUsd: ticks / TICKS_PER_USD } : null;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Image API response usage (OpenRouter / NanoGPT `POST /images`). `usage.cost`
|
|
78
|
+
* is USD; NanoGPT's top-level `cost` counts only when `paymentSource` says USD
|
|
79
|
+
* (it may be billed in XNO).
|
|
80
|
+
*/
|
|
81
|
+
export function imageApiUsage(body) {
|
|
82
|
+
const usage = body?.usage && typeof body.usage === 'object' ? body.usage : {};
|
|
83
|
+
const tokens = tokenUsage({
|
|
84
|
+
inputTokens: count(usage.prompt_tokens),
|
|
85
|
+
outputTokens: count(usage.completion_tokens),
|
|
86
|
+
outputImageTokens: count(usage.completion_tokens_details?.image_tokens),
|
|
87
|
+
});
|
|
88
|
+
const usd = String(body?.paymentSource || '').toUpperCase() === 'USD' ? body?.cost : undefined;
|
|
89
|
+
const cost = [usage.cost, usd].find((value) => typeof value === 'number' && Number.isFinite(value) && value >= 0);
|
|
90
|
+
return cost === undefined ? tokens : { ...tokens, costUsd: cost };
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
/** True when the provider reported something billable for the request. */
|
|
77
94
|
export function hasReportedUsage(usage) {
|
|
78
95
|
if (!usage) return false;
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
* what made the remote gallery slow.
|
|
15
15
|
*/
|
|
16
16
|
import { spawn } from 'node:child_process';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { dirname, join } from 'node:path';
|
|
17
|
+
import { existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
20
19
|
|
|
20
|
+
import { writeFileAtomicSync } from '../shared/atomic-file.mjs';
|
|
21
|
+
import { fileHasData } from '../shared/file-has-data.mjs';
|
|
21
22
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
22
23
|
|
|
23
24
|
// maxEdge is the long edge in pixels; quality is the webp setting.
|
|
@@ -100,7 +101,14 @@ function cachedRendition(cacheDir, variant, id) {
|
|
|
100
101
|
// a plain cache miss into a thrown media request.
|
|
101
102
|
try {
|
|
102
103
|
const info = statSync(path);
|
|
103
|
-
if (info.isFile())
|
|
104
|
+
if (!info.isFile()) continue;
|
|
105
|
+
// A rendition a crash left empty or NUL-filled would be served forever;
|
|
106
|
+
// drop it so the miss below rebuilds it.
|
|
107
|
+
if (!fileHasData(path)) {
|
|
108
|
+
unlinkSync(path);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
return { path, mime, bytes: info.size };
|
|
104
112
|
} catch {
|
|
105
113
|
/* absent or evicted mid-lookup */
|
|
106
114
|
}
|
|
@@ -166,21 +174,13 @@ function writeRendition(cacheDir, variant, id, extension, buffer) {
|
|
|
166
174
|
return null;
|
|
167
175
|
}
|
|
168
176
|
const path = renditionPath(cacheDir, variant, id, extension);
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
const staging = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
172
|
-
writeFileSync(staging, buffer);
|
|
177
|
+
// Durable stage-then-rename: a reader never observes a half-written cache
|
|
178
|
+
// file, and a crash cannot publish one whose bytes are all NUL.
|
|
173
179
|
try {
|
|
174
|
-
|
|
180
|
+
writeFileAtomicSync(path, buffer);
|
|
175
181
|
} catch (error) {
|
|
176
182
|
// Another process may have won the same cache publication race.
|
|
177
183
|
if (!existsSync(path)) throw error;
|
|
178
|
-
} finally {
|
|
179
|
-
try {
|
|
180
|
-
unlinkSync(staging);
|
|
181
|
-
} catch {
|
|
182
|
-
/* renamed or already removed */
|
|
183
|
-
}
|
|
184
184
|
}
|
|
185
185
|
let bytes = buffer.length;
|
|
186
186
|
try {
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* (newest first) and is written atomically under a file lock because the
|
|
7
7
|
* desktop and CLI can generate concurrently.
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync,
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, statSync } from 'node:fs';
|
|
10
10
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
11
11
|
import { randomUUID } from 'node:crypto';
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
13
|
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import { readJsonStateSync, updateJsonAtomicSync, writeFileAtomicSync } from '../shared/atomic-file.mjs';
|
|
15
15
|
import { cacheRendition, ensureRendition, removeRenditions, renditionSpec } from './renditions.mjs';
|
|
16
16
|
|
|
17
17
|
// Renderer transport is base64 over IPC: refuse to inline anything larger.
|
|
@@ -50,10 +50,6 @@ function indexPath() {
|
|
|
50
50
|
return join(mediaDir(), 'index.json');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function indexLockPath() {
|
|
54
|
-
return `${indexPath()}.lock`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
53
|
function ensureDirs({ migrate = false } = {}) {
|
|
58
54
|
mkdirSync(assetsDir(), { recursive: true });
|
|
59
55
|
if (migrate && !layoutMigrationChecked) {
|
|
@@ -89,42 +85,45 @@ function readIndex() {
|
|
|
89
85
|
indexCacheHits += 1;
|
|
90
86
|
return indexCacheAssets;
|
|
91
87
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
const parsed = JSON.parse(raw);
|
|
103
|
-
if (!parsed || !Array.isArray(parsed.assets)) {
|
|
104
|
-
throw new Error(`media index is malformed: ${path}`);
|
|
105
|
-
}
|
|
106
|
-
indexDiskReads += 1;
|
|
107
|
-
return rememberIndex(path, signature, parsed.assets);
|
|
88
|
+
// A missing index is a genuinely empty catalog (first run). A DAMAGED one
|
|
89
|
+
// (empty, NUL-filled, truncated, wrong shape) is kept as index.json.corrupt-<ts>
|
|
90
|
+
// and the last good copy restored, so the gallery lists and saves keep
|
|
91
|
+
// working. Every other read failure (EACCES, EIO) still throws: answering
|
|
92
|
+
// with an empty list made the next save/delete write that emptiness back
|
|
93
|
+
// over every existing asset entry.
|
|
94
|
+
const assets = readJsonStateSync(path, { validate: catalogAssets, fallback: [] });
|
|
95
|
+
if (signature !== 'missing') indexDiskReads += 1;
|
|
96
|
+
return rememberIndex(path, signature, assets);
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
rememberIndex(path, indexSignature(path), assets);
|
|
99
|
+
function catalogAssets(parsed) {
|
|
100
|
+
if (!parsed || !Array.isArray(parsed.assets)) throw new Error(`media index is malformed: ${indexPath()}`);
|
|
101
|
+
return parsed.assets;
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
/**
|
|
117
105
|
* The one catalog read-modify-write. Every mutation (migrate, save, duration
|
|
118
|
-
* backfill, delete) holds index.lock across a fresh
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
106
|
+
* backfill, delete) holds index.lock across a fresh read and writes back only
|
|
107
|
+
* what the callback returns, so a concurrent CLI/desktop generation can never
|
|
108
|
+
* lose an entry — and a read that FAILS (rather than being absent) propagates
|
|
109
|
+
* out of the lock instead of writing an empty catalog over it. The previous
|
|
110
|
+
* catalog is kept as index.json.bak.
|
|
122
111
|
*/
|
|
123
112
|
function mutateIndex(mutate) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
// Sets a wrong-shaped catalog aside before the lock below takes the file.
|
|
114
|
+
readIndex();
|
|
115
|
+
let written = null;
|
|
116
|
+
updateJsonAtomicSync(
|
|
117
|
+
indexPath(),
|
|
118
|
+
(current) => {
|
|
119
|
+
const next = mutate(Array.isArray(current?.assets) ? current.assets : []);
|
|
120
|
+
if (!next) return undefined;
|
|
121
|
+
written = next;
|
|
122
|
+
return { version: 2, assets: next };
|
|
123
|
+
},
|
|
124
|
+
{ backup: true }
|
|
125
|
+
);
|
|
126
|
+
if (written) rememberIndex(indexPath(), indexSignature(indexPath()), written);
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
export function mediaStoreCacheStats() {
|
|
@@ -228,7 +227,9 @@ export function saveMediaAsset({ kind, lane, model, prompt, options = {}, mime,
|
|
|
228
227
|
const path = storedAssetPath(file);
|
|
229
228
|
if (!path) throw new Error('invalid media asset path');
|
|
230
229
|
mkdirSync(dirname(path), { recursive: true });
|
|
231
|
-
|
|
230
|
+
// Durable before the index names it: a crash must not leave a listed asset
|
|
231
|
+
// whose bytes are all NUL.
|
|
232
|
+
writeFileAtomicSync(path, bytes);
|
|
232
233
|
entry.file = file;
|
|
233
234
|
// The gallery is NOT capped (user). An entry-count trim used to unlink the
|
|
234
235
|
// oldest files here, which is data loss nobody asked for: a generated asset
|
|
@@ -28,6 +28,7 @@ const IMAGE_MIME = Object.freeze({
|
|
|
28
28
|
'.gif': 'image/gif',
|
|
29
29
|
});
|
|
30
30
|
const MAX_REFERENCE_BYTES = 12 * 1024 * 1024;
|
|
31
|
+
const GATEWAY_IMAGE_LANES = new Set(['openrouter', 'nano-gpt']);
|
|
31
32
|
|
|
32
33
|
let graph = null;
|
|
33
34
|
async function mediaGraph(deps) {
|
|
@@ -228,7 +229,13 @@ export function buildOptions(
|
|
|
228
229
|
if (resolution) {
|
|
229
230
|
validateChoice(controls, 'resolution', resolution, 'resolution');
|
|
230
231
|
options.resolution = resolution;
|
|
231
|
-
} else if (
|
|
232
|
+
} else if (
|
|
233
|
+
kind === 'image' &&
|
|
234
|
+
!GATEWAY_IMAGE_LANES.has(lane.id) &&
|
|
235
|
+
allowed(controls, 'resolution')?.includes('2k')
|
|
236
|
+
) {
|
|
237
|
+
// Gateway models bill per resolution tier: an omitted resolution keeps
|
|
238
|
+
// the provider's default there instead of silently upscaling.
|
|
232
239
|
options.resolution = '2k';
|
|
233
240
|
}
|
|
234
241
|
if (quality) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
2
2
|
|
|
3
3
|
const BACKLOG_WARN_COOLDOWN_MS = 10 * 60_000;
|
|
4
4
|
const BACKLOG_WARN_FAILURES = 5;
|
|
@@ -16,7 +16,13 @@ export function createCycleHealthLedger({ cycleStateFile, log }) {
|
|
|
16
16
|
|
|
17
17
|
function write() {
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
// Rewritten on every cycle event and read by a statusline that treats an
|
|
20
|
+
// unparsable file as "nothing to show", so no fsync; rename keeps it untorn.
|
|
21
|
+
writeJsonAtomicSync(
|
|
22
|
+
cycleStateFile,
|
|
23
|
+
{ running, backlog, cycles: health, updatedAt: Date.now() },
|
|
24
|
+
{ compact: true, fsync: false }
|
|
25
|
+
);
|
|
20
26
|
} catch {
|
|
21
27
|
/* best-effort; statusline just shows nothing */
|
|
22
28
|
}
|