mixdog 1.4.0 → 1.4.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/package.json +1 -1
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +12 -3
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +37 -37
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +123 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -4
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test-data-dir.mjs +10 -7
- package/src/app.mjs +4 -0
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -4
- package/src/defaults/skills/computer-use/SKILL.md +4 -2
- package/src/lib/rules-builder.cjs +29 -12
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +10 -5
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/70-delivery.md +7 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/registry.mjs +12 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +61 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +5 -1
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +3 -0
- package/src/runtime/shared/tool-work-units.mjs +2 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +15 -0
- package/src/runtime/shared/worktrees/manager.mjs +87 -38
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/visualize-bridge/client.mjs +2 -0
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +152 -63
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +5 -1
- package/src/session-runtime/bot-status.mjs +12 -6
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +271 -25
- package/src/session-runtime/conversation-import/service.mjs +26 -5
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/provider-admin.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/transitions.mjs +25 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
- package/src/standalone/daemon-session-control.mjs +18 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/app/app-view/floating-panel.jsx +7 -4
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +9 -5
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/dist/index.mjs +75 -28
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/ui/statusline-agents.mjs +1 -3
|
@@ -236,34 +236,36 @@ export function resolveOfficeArtDirection(
|
|
|
236
236
|
String(format || '').toLowerCase() === 'xlsx'
|
|
237
237
|
? DIRECTION_BLUEPRINTS.filter((blueprint) => blueprint.backgroundMode !== 'dark')
|
|
238
238
|
: DIRECTION_BLUEPRINTS;
|
|
239
|
-
const candidates = blueprints
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
palette,
|
|
248
|
-
typography: { ...blueprint.typography },
|
|
249
|
-
style: { ...blueprint.style, pageRhythm: [...blueprint.style.pageRhythm] },
|
|
250
|
-
creativeSystem: creativeSystemOf(blueprint),
|
|
251
|
-
deck: {
|
|
252
|
-
backgroundMode: blueprint.backgroundMode,
|
|
239
|
+
const candidates = blueprints
|
|
240
|
+
.map((blueprint) => {
|
|
241
|
+
const palette = directionPalette(baseHue, blueprint);
|
|
242
|
+
const motif = rawSubject ? `${blueprint.motif} · ${rawSubject}` : blueprint.motif;
|
|
243
|
+
return {
|
|
244
|
+
id: blueprint.id,
|
|
245
|
+
label: blueprint.label,
|
|
246
|
+
rationale: `${blueprint.label} fits ${purpose} through ${blueprint.motif}.`,
|
|
253
247
|
motif,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
...
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
248
|
+
palette,
|
|
249
|
+
typography: { ...blueprint.typography },
|
|
250
|
+
style: { ...blueprint.style, pageRhythm: [...blueprint.style.pageRhythm] },
|
|
251
|
+
creativeSystem: creativeSystemOf(blueprint),
|
|
252
|
+
deck: {
|
|
253
|
+
backgroundMode: blueprint.backgroundMode,
|
|
254
|
+
motif,
|
|
255
|
+
imageTreatment: blueprint.imageTreatment,
|
|
256
|
+
layoutBias: [...blueprint.layoutBias],
|
|
257
|
+
...creativeSystemOf(blueprint),
|
|
258
|
+
},
|
|
259
|
+
score: directionScore(blueprint, {
|
|
260
|
+
purpose,
|
|
261
|
+
expressionMode,
|
|
262
|
+
tone: String(request.tone || ''),
|
|
263
|
+
requestedId: String(requested.id || ''),
|
|
264
|
+
seed,
|
|
265
|
+
}),
|
|
266
|
+
};
|
|
267
|
+
})
|
|
268
|
+
.sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
|
|
267
269
|
const selected = candidates[0];
|
|
268
270
|
const hasSubject = Boolean(rawSubject || requested.seed || requested.id);
|
|
269
271
|
const disabled = request.artDirection === false || requested.enabled === false;
|
|
@@ -890,7 +890,12 @@ function drawChartBlock(flow, block, following) {
|
|
|
890
890
|
});
|
|
891
891
|
flow.y -= rowH;
|
|
892
892
|
});
|
|
893
|
-
page.drawLine({
|
|
893
|
+
page.drawLine({
|
|
894
|
+
start: { x: left, y: top },
|
|
895
|
+
end: { x: left, y: flow.y },
|
|
896
|
+
thickness: 0.6,
|
|
897
|
+
color: color(flow.ink.line),
|
|
898
|
+
});
|
|
894
899
|
} else {
|
|
895
900
|
const baseline = flow.y - size * 1.6 - layout.plotH;
|
|
896
901
|
values.forEach((value, index) => {
|
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
xmlDecode,
|
|
13
13
|
} from './portable-xml.mjs';
|
|
14
14
|
import { countDocxPropertyChanges, docxRevisionTree, flattenDocxRevisions } from './docx-revisions.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
docxParagraphSpacing,
|
|
17
|
+
docxRunFont,
|
|
18
|
+
nextPageOffset,
|
|
19
|
+
softBreakFacts,
|
|
20
|
+
statedRunFont,
|
|
21
|
+
} from './portable-snapshot-shared.mjs';
|
|
16
22
|
|
|
17
23
|
const REVISION_TYPES = Object.freeze({
|
|
18
24
|
ins: 'insertion',
|
|
@@ -123,7 +123,12 @@ export function populatedCellPagination({ options, page, selectedSheets, sheets,
|
|
|
123
123
|
|
|
124
124
|
export function statedRunFont(xml) {
|
|
125
125
|
const direct = docxRunFont(xml);
|
|
126
|
-
return direct.size ||
|
|
126
|
+
return direct.size ||
|
|
127
|
+
direct.bold ||
|
|
128
|
+
direct.name ||
|
|
129
|
+
direct.color ||
|
|
130
|
+
direct.caps !== undefined ||
|
|
131
|
+
direct.letterSpacing !== undefined
|
|
127
132
|
? { font: direct }
|
|
128
133
|
: {};
|
|
129
134
|
}
|
|
@@ -152,7 +152,13 @@ export async function reviewRenderedOfficePages(
|
|
|
152
152
|
// The cover/title page is short by design, and a page followed by a deliberate forced break ends where the
|
|
153
153
|
// author ended it.
|
|
154
154
|
const endsBeforeForcedBreak = forcedBreaks?.has(metric.page + 1) === true;
|
|
155
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
flowing &&
|
|
157
|
+
metric.page > 1 &&
|
|
158
|
+
!endsBeforeForcedBreak &&
|
|
159
|
+
metric.page < lastPage &&
|
|
160
|
+
metric.bodyEnd < PAGE_BODY_END_MIN
|
|
161
|
+
) {
|
|
156
162
|
const empty = Math.round((1 - metric.bodyEnd) * 100);
|
|
157
163
|
issues.push(
|
|
158
164
|
issue(
|
|
@@ -166,7 +172,8 @@ export async function reviewRenderedOfficePages(
|
|
|
166
172
|
continue;
|
|
167
173
|
}
|
|
168
174
|
// A short chapter is the author's: the page ends before a forced break, or it is the last page and opens with one.
|
|
169
|
-
const deliberateShort =
|
|
175
|
+
const deliberateShort =
|
|
176
|
+
endsBeforeForcedBreak || (forcedBreaks?.has(metric.page) === true && metric.page >= lastPage);
|
|
170
177
|
if (
|
|
171
178
|
['docx', 'pdf'].includes(normalized) &&
|
|
172
179
|
metric.page > 1 &&
|
|
@@ -12,7 +12,9 @@ function paragraphSize(paragraph) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const median = (values) => {
|
|
15
|
-
const sorted = values
|
|
15
|
+
const sorted = values
|
|
16
|
+
.filter((value) => value !== '' && value !== undefined && value !== null)
|
|
17
|
+
.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
16
18
|
return sorted.length ? sorted[Math.floor(sorted.length / 2)] : undefined;
|
|
17
19
|
};
|
|
18
20
|
// Automatic colour is the effective default, black.
|
|
@@ -12,11 +12,7 @@ import {
|
|
|
12
12
|
saturatedHueFamilies,
|
|
13
13
|
} from '../design/design-discipline.mjs';
|
|
14
14
|
import { authoredBackgroundLadder, isCardGridSlide, isOrnamentalStripe, slideSize } from './design-review-authored.mjs';
|
|
15
|
-
import {
|
|
16
|
-
reviewBriefPromises,
|
|
17
|
-
reviewFactCoverage,
|
|
18
|
-
reviewSourceGrounding,
|
|
19
|
-
} from '../authoring/pptx-brief.mjs';
|
|
15
|
+
import { reviewBriefPromises, reviewFactCoverage, reviewSourceGrounding } from '../authoring/pptx-brief.mjs';
|
|
20
16
|
import { isAdvisoryOfficeIssue } from './quality-pipeline.mjs';
|
|
21
17
|
import { issue } from './assurance-issue.mjs';
|
|
22
18
|
import { isPptxSpecimenSlide, isPptxStatementSlide } from './pptx-slide-roles.mjs';
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// A Word document or workbook held to its brief, as an authored deck is: every figure its words show has a fact with
|
|
2
2
|
// a source, and a fact cites where a reader can open it. The brief is the deck's (`design.brief`, key: value lines);
|
|
3
3
|
// the readings are the deck's own (pptx-brief.mjs), over the document's text instead of its slides.
|
|
4
|
-
import {
|
|
5
|
-
parseAuthoringBrief,
|
|
6
|
-
reviewFactCoverage,
|
|
7
|
-
reviewSourceGrounding,
|
|
8
|
-
} from '../authoring/pptx-brief.mjs';
|
|
4
|
+
import { parseAuthoringBrief, reviewFactCoverage, reviewSourceGrounding } from '../authoring/pptx-brief.mjs';
|
|
9
5
|
import { htmlBriefScript } from '../authoring/pptx-html-runner.mjs';
|
|
10
6
|
|
|
11
7
|
/** The brief a docx/xlsx call names in design.brief (the HTML BRIEF's key: value lines), or null. */
|
|
@@ -25,9 +25,7 @@ const visibleText = (html) =>
|
|
|
25
25
|
|
|
26
26
|
// A media query list reaches the printed page unless every query is screen-only.
|
|
27
27
|
const appliesToPrint = (queries) =>
|
|
28
|
-
queries
|
|
29
|
-
.split(',')
|
|
30
|
-
.some((query) => !/^\s*(?:only\s+)?screen\b/i.test(query) && !/^\s*not\s+print\b/i.test(query));
|
|
28
|
+
queries.split(',').some((query) => !/^\s*(?:only\s+)?screen\b/i.test(query) && !/^\s*not\s+print\b/i.test(query));
|
|
31
29
|
|
|
32
30
|
// Style rules in source order, flattened out of @media blocks that do not apply in print.
|
|
33
31
|
function cssRules(css, active, rules) {
|
|
@@ -38,7 +36,10 @@ function cssRules(css, active, rules) {
|
|
|
38
36
|
let depth = 1;
|
|
39
37
|
let close = open + 1;
|
|
40
38
|
for (; close < css.length && depth > 0; close += 1) depth += css[close] === '{' ? 1 : css[close] === '}' ? -1 : 0;
|
|
41
|
-
const head = css
|
|
39
|
+
const head = css
|
|
40
|
+
.slice(index, open)
|
|
41
|
+
.replace(/^(?:\s*@[^{;]*;)+/, '')
|
|
42
|
+
.trim();
|
|
42
43
|
const inner = css.slice(open + 1, close - 1);
|
|
43
44
|
if (/^@media\b/i.test(head)) cssRules(inner, active && appliesToPrint(head.slice(6)), rules);
|
|
44
45
|
else if (active && head && !head.startsWith('@')) rules.push({ head, body: inner });
|
|
@@ -64,7 +65,8 @@ function breakDeclaration(body, rank) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const beats = (a, b) => {
|
|
67
|
-
for (let index = 0; index < a.rank.length; index += 1)
|
|
68
|
+
for (let index = 0; index < a.rank.length; index += 1)
|
|
69
|
+
if (a.rank[index] !== b.rank[index]) return a.rank[index] > b.rank[index];
|
|
68
70
|
return true;
|
|
69
71
|
};
|
|
70
72
|
|
|
@@ -80,7 +82,8 @@ function breakSelectors(html) {
|
|
|
80
82
|
const classes = match[3].split('.').filter(Boolean);
|
|
81
83
|
const specificity = (match[2] ? 100 : 0) + classes.length * 10 + (match[1] ? 1 : 0);
|
|
82
84
|
const declaration = breakDeclaration(body, [0, specificity, order]);
|
|
83
|
-
if (declaration)
|
|
85
|
+
if (declaration)
|
|
86
|
+
selectors.push({ tag: (match[1] || '').toLowerCase(), id: match[2] || '', classes, declaration });
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
}
|
|
@@ -289,7 +289,7 @@ const POLISH_GUIDANCE = Object.freeze({
|
|
|
289
289
|
theme_background_drift:
|
|
290
290
|
'Use only the ladder backgrounds (paper, paperAlt, dark, darkAlt); recolor the drifting slide.',
|
|
291
291
|
theme_body_backgrounds:
|
|
292
|
-
|
|
292
|
+
"Advisory: body slides use several background colours; keep them to the concept's declared palette ladder, or declare the concept or palette in the brief when the colour fields are the design.",
|
|
293
293
|
excessive_slide_text: "Cut the copy to the reading mode's budget (composition.md §4) or split the slide.",
|
|
294
294
|
decorative_stripe: 'Remove the ornamental bar; a stripe that encodes nothing is decoration (composition.md §10).',
|
|
295
295
|
// Visual critique contract (finalize).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Bot-owned work: the live work in a session that a bot sent. A bot's accepted
|
|
2
|
+
// `session` send or create (session-runtime/agent-task-sessions.mjs) is the
|
|
3
|
+
// bot's work in that session from its acceptance until it settles or is
|
|
4
|
+
// cancelled — whether or not it reports back (`notify: false`, a report watch
|
|
5
|
+
// that could not be stored). A turn nobody sent (the user typed it in the
|
|
6
|
+
// task session) is owned by no bot, so it never reads as a bot being busy.
|
|
7
|
+
// Process-wide and dependency-free like delegated-approval-host.mjs. The
|
|
8
|
+
// registry is only this process's cache: what keeps the work owned across a
|
|
9
|
+
// restart is stored apart (a reporting send's watch, any other send's record
|
|
10
|
+
// of its work, both in agent-home-delivery.mjs) and re-noted by
|
|
11
|
+
// agent-task-sessions.mjs recover. A change of which bots own a session's work
|
|
12
|
+
// is announced (session-activity-signal.mjs onSessionBotWorkChange), so the
|
|
13
|
+
// session catalog, which stamps them on each row, re-lists.
|
|
14
|
+
import { emitSessionBotWorkChange } from './session-activity-signal.mjs';
|
|
15
|
+
|
|
16
|
+
const KEY = Symbol.for('mixdog.botOwnedWork.v1');
|
|
17
|
+
|
|
18
|
+
/** Map run id → `{ sessionId, agentId }`. */
|
|
19
|
+
function registry() {
|
|
20
|
+
globalThis[KEY] ??= new Map();
|
|
21
|
+
return globalThis[KEY];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** How many owned works of `agentId` run in `sessionId`. */
|
|
25
|
+
function worksOf(sessionId, agentId) {
|
|
26
|
+
let count = 0;
|
|
27
|
+
for (const work of registry().values()) if (work.sessionId === sessionId && work.agentId === agentId) count += 1;
|
|
28
|
+
return count;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Every session with owned work and the bots owning it (each once, in noting
|
|
32
|
+
* order): Map session → agent ids. */
|
|
33
|
+
export function botWorkBySession() {
|
|
34
|
+
const bySession = new Map();
|
|
35
|
+
for (const { sessionId, agentId } of registry().values()) {
|
|
36
|
+
const agents = bySession.get(sessionId) ?? [];
|
|
37
|
+
if (!agents.includes(agentId)) agents.push(agentId);
|
|
38
|
+
bySession.set(sessionId, agents);
|
|
39
|
+
}
|
|
40
|
+
return bySession;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Agent `agentId`'s submission `runId` to `sessionId` was accepted. */
|
|
44
|
+
export function noteBotWork({ runId, sessionId, agentId }) {
|
|
45
|
+
if (!runId || !sessionId || !agentId || registry().has(String(runId))) return;
|
|
46
|
+
registry().set(String(runId), { sessionId: String(sessionId), agentId: String(agentId) });
|
|
47
|
+
if (worksOf(String(sessionId), String(agentId)) === 1) emitSessionBotWorkChange(sessionId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Submission `runId` settled or was cancelled: its work is nobody's now. */
|
|
51
|
+
export function endBotWork(runId) {
|
|
52
|
+
const work = registry().get(String(runId));
|
|
53
|
+
if (!work) return;
|
|
54
|
+
registry().delete(String(runId));
|
|
55
|
+
if (worksOf(work.sessionId, work.agentId) === 0) emitSessionBotWorkChange(work.sessionId);
|
|
56
|
+
}
|
|
@@ -176,10 +176,7 @@ function removeLegacyKeys(root, dataDir) {
|
|
|
176
176
|
* not set the marker. `stripRouteDefaults` drops shipped presets and
|
|
177
177
|
* maintenance routes (their defaults live with the agent config).
|
|
178
178
|
*/
|
|
179
|
-
function separateDefaultsInConfig(
|
|
180
|
-
root,
|
|
181
|
-
{ stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}
|
|
182
|
-
) {
|
|
179
|
+
function separateDefaultsInConfig(root, { stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}) {
|
|
183
180
|
const from = Number(root?.[DEFAULTS_VERSION_KEY]) || 0;
|
|
184
181
|
let next = { ...(root || {}) };
|
|
185
182
|
if (from < 1) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
|
|
3
|
+
export const FETCH_BLOCKED_PORTS: ReadonlySet<number>;
|
|
4
|
+
|
|
5
|
+
export const FETCH_SAFE_ATTEMPTS: number;
|
|
6
|
+
|
|
7
|
+
export function listenOnFetchSafePort(
|
|
8
|
+
server: Server,
|
|
9
|
+
host: string,
|
|
10
|
+
onListening: () => void,
|
|
11
|
+
options?: {
|
|
12
|
+
attempts?: number;
|
|
13
|
+
isCurrent?: () => boolean;
|
|
14
|
+
portOf?: (server: Server) => number;
|
|
15
|
+
}
|
|
16
|
+
): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Ports the WHATWG Fetch spec blocks ("bad port"): fetch (undici) and Chromium
|
|
2
|
+
// refuse to connect to them, so a loopback server that a fetch client or a
|
|
3
|
+
// browser context must reach cannot live on one. https://fetch.spec.whatwg.org/#port-blocking
|
|
4
|
+
export const FETCH_BLOCKED_PORTS = new Set([
|
|
5
|
+
0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110,
|
|
6
|
+
111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532,
|
|
7
|
+
540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061,
|
|
8
|
+
6000, 6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080,
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
/** How many more OS-assigned ports a listener takes after a blocked one. */
|
|
12
|
+
export const FETCH_SAFE_ATTEMPTS = 16;
|
|
13
|
+
|
|
14
|
+
const assignedPort = (server) => {
|
|
15
|
+
const address = server.address();
|
|
16
|
+
return address && typeof address === 'object' ? address.port : 0;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `server.listen(0, host)` that never settles on a fetch-blocked port: when the
|
|
21
|
+
* OS assigns one, the server is closed and listened again, at most `attempts`
|
|
22
|
+
* more times. `onListening` runs once, on a safe port. When the attempts run
|
|
23
|
+
* out the server is closed and an 'error' is emitted, like a failed listen.
|
|
24
|
+
* `isCurrent` lets a caller that stopped the server meanwhile cancel the retry;
|
|
25
|
+
* `portOf` reads the assigned port.
|
|
26
|
+
*/
|
|
27
|
+
export function listenOnFetchSafePort(
|
|
28
|
+
server,
|
|
29
|
+
host,
|
|
30
|
+
onListening,
|
|
31
|
+
{ attempts = FETCH_SAFE_ATTEMPTS, isCurrent = () => true, portOf = assignedPort } = {}
|
|
32
|
+
) {
|
|
33
|
+
let retries = attempts;
|
|
34
|
+
const attempt = () => {
|
|
35
|
+
server.listen(0, host, () => {
|
|
36
|
+
if (!FETCH_BLOCKED_PORTS.has(portOf(server))) {
|
|
37
|
+
onListening();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const exhausted = retries <= 0;
|
|
41
|
+
retries -= 1;
|
|
42
|
+
server.close(() => {
|
|
43
|
+
if (!isCurrent()) return;
|
|
44
|
+
if (exhausted) server.emit('error', new Error('no fetch-safe loopback port was assigned'));
|
|
45
|
+
else attempt();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
attempt();
|
|
50
|
+
}
|
|
@@ -8,7 +8,8 @@ export interface HandoffFile {
|
|
|
8
8
|
id?: string;
|
|
9
9
|
}
|
|
10
10
|
export function normalizeHandoffFiles(files: unknown): HandoffFile[];
|
|
11
|
-
export function artifactLines(files: unknown, omitted?: number
|
|
11
|
+
export function artifactLines(files: unknown, omitted?: number): string[];
|
|
12
|
+
export function producedFileLines(files: unknown, omitted?: number): string[];
|
|
12
13
|
export interface HandoffRecord {
|
|
13
14
|
id: string;
|
|
14
15
|
attachmentRef: string;
|
|
@@ -31,20 +31,30 @@ export function normalizeHandoffFiles(files) {
|
|
|
31
31
|
}));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
* lists paths only — an id named in text is never trusted. */
|
|
38
|
-
export function artifactLines(files, omitted = 0, { ids = true } = {}) {
|
|
34
|
+
/** An agent completion's compact list of files it produced (name and path
|
|
35
|
+
* only; the Lead forwards or links by path), empty when there are none. */
|
|
36
|
+
export function producedFileLines(files, omitted = 0) {
|
|
39
37
|
const list = normalizeHandoffFiles(files);
|
|
40
38
|
if (!list.length) return [];
|
|
41
|
-
const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
|
|
42
39
|
return [
|
|
43
|
-
|
|
40
|
+
'Files produced:',
|
|
41
|
+
...list.map((file) => `- ${oneLine(file.name)} — ${oneLine(file.path)}`),
|
|
42
|
+
...(omitted > 0 ? [`- …and ${omitted} more`] : []),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The model-facing list of a bot task report's `files`, empty when there are
|
|
47
|
+
* none; the row also records the stored files as its attachments, so their
|
|
48
|
+
* ids can be forwarded. */
|
|
49
|
+
export function artifactLines(files, omitted = 0) {
|
|
50
|
+
const list = normalizeHandoffFiles(files);
|
|
51
|
+
if (!list.length) return [];
|
|
52
|
+
return [
|
|
53
|
+
'Files from this turn (forward one with attachments: [id or path]; link a path to show it):',
|
|
44
54
|
...list.map(
|
|
45
55
|
(file) =>
|
|
46
56
|
`- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
|
|
47
|
-
file.id ?
|
|
57
|
+
file.id ? ` [attachment:${file.id}]` : ' (too large or unsupported to attach; link the path)'
|
|
48
58
|
}`
|
|
49
59
|
),
|
|
50
60
|
...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
|
|
@@ -44,15 +44,37 @@ function hasModelVisibleConversation(session) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
47
|
+
// A session's last successful cache-warm use (a read with no write),
|
|
48
|
+
// registered by the warmer (cache-warm.mjs, from its durable touch store):
|
|
49
|
+
// `{ at, provider, model, accountId }` — when, and the route and account whose
|
|
50
|
+
// cached prefix it read — or a bare epoch-ms time; null/0 when none.
|
|
51
|
+
let lastCacheWarmUse = () => null;
|
|
51
52
|
|
|
52
53
|
/** Register the source of a session's last successful cache-warm use
|
|
53
|
-
* (`(sessionId) => epoch ms |
|
|
54
|
+
* (`(sessionId) => { at, provider, model, accountId } | epoch ms | null`);
|
|
55
|
+
* null restores none. */
|
|
54
56
|
export function setCacheWarmUseSource(source) {
|
|
55
|
-
lastCacheWarmUse = typeof source === 'function' ? source : () =>
|
|
57
|
+
lastCacheWarmUse = typeof source === 'function' ? source : () => null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** When a successful cache warm last used the session's prompt cache (epoch
|
|
61
|
+
* ms), or 0: the warm read and refreshed the cached prefix without moving
|
|
62
|
+
* any of the session's own timestamps. */
|
|
63
|
+
export function lastCacheWarmUseAt(sessionId) {
|
|
64
|
+
const use = sessionId ? lastCacheWarmUse(sessionId) : null;
|
|
65
|
+
return timestampMs(use && typeof use === 'object' ? use.at : use);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** When a successful cache warm last used the prefix a request on `provider`
|
|
69
|
+
* / `model` / `accountId` reads (epoch ms), or 0: a warm on another route or
|
|
70
|
+
* account refreshed an entry this request cannot read, and a use without a
|
|
71
|
+
* recorded identity matches nothing. */
|
|
72
|
+
export function cacheWarmUseFor(sessionId, { provider, model, accountId = '' } = {}) {
|
|
73
|
+
const use = sessionId ? lastCacheWarmUse(sessionId) : null;
|
|
74
|
+
if (!use || typeof use !== 'object' || !use.provider) return 0;
|
|
75
|
+
const matches =
|
|
76
|
+
use.provider === provider && use.model === model && String(use.accountId || '') === String(accountId || '');
|
|
77
|
+
return matches ? timestampMs(use.at) : 0;
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
/**
|
|
@@ -65,7 +87,7 @@ export function setCacheWarmUseSource(source) {
|
|
|
65
87
|
export function sessionActivityTimestamp(session, fallback = 0) {
|
|
66
88
|
if (!hasModelVisibleConversation(session)) return 0;
|
|
67
89
|
const own = timestampMs(session?.lastUsedAt) || timestampMs(session?.updatedAt) || timestampMs(fallback);
|
|
68
|
-
return Math.max(own,
|
|
90
|
+
return Math.max(own, lastCacheWarmUseAt(session?.id));
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
// A zero is often an unavailable/stale meter field, not an authoritative
|
|
@@ -100,7 +100,11 @@ export function priceUsage(args) {
|
|
|
100
100
|
// A provider-billed figure for a media request (e.g. xAI cost_in_usd_ticks)
|
|
101
101
|
// is the price, with or without a catalog row.
|
|
102
102
|
const media = args.media || null;
|
|
103
|
-
if (
|
|
103
|
+
if (
|
|
104
|
+
typeof media?.reportedCostUsd === 'number' &&
|
|
105
|
+
Number.isFinite(media.reportedCostUsd) &&
|
|
106
|
+
media.reportedCostUsd >= 0
|
|
107
|
+
)
|
|
104
108
|
return {
|
|
105
109
|
input,
|
|
106
110
|
costUsd: Number(media.reportedCostUsd.toFixed(6)),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { FETCH_BLOCKED_PORTS, FETCH_SAFE_ATTEMPTS } from './fetch-safe-listen.mjs';
|
|
2
|
+
|
|
1
3
|
// One loopback listener owns each bind attempt, its event handlers, and stop.
|
|
2
4
|
export function createLoopbackListener({
|
|
3
5
|
server,
|
|
@@ -24,7 +26,15 @@ export function createLoopbackListener({
|
|
|
24
26
|
|
|
25
27
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
26
28
|
const controller = new AbortController();
|
|
27
|
-
const attempt = {
|
|
29
|
+
const attempt = {
|
|
30
|
+
promise,
|
|
31
|
+
reject,
|
|
32
|
+
controller,
|
|
33
|
+
port: basePort,
|
|
34
|
+
bound: false,
|
|
35
|
+
blockedRetries: FETCH_SAFE_ATTEMPTS,
|
|
36
|
+
detach,
|
|
37
|
+
};
|
|
28
38
|
current = attempt;
|
|
29
39
|
|
|
30
40
|
function detach() {
|
|
@@ -35,6 +45,19 @@ export function createLoopbackListener({
|
|
|
35
45
|
function onListening() {
|
|
36
46
|
if (current !== attempt) return;
|
|
37
47
|
const boundPort = server.address().port;
|
|
48
|
+
// An OS-assigned port can be one fetch clients refuse ("bad port"):
|
|
49
|
+
// close it and take another, a bounded number of times.
|
|
50
|
+
if (attempt.port === 0 && FETCH_BLOCKED_PORTS.has(boundPort)) {
|
|
51
|
+
const exhausted = attempt.blockedRetries <= 0;
|
|
52
|
+
attempt.blockedRetries -= 1;
|
|
53
|
+
server.close(() => {
|
|
54
|
+
if (current !== attempt) return;
|
|
55
|
+
if (exhausted) onError(new Error('no fetch-safe loopback port was assigned'));
|
|
56
|
+
else listen();
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
server.off('listening', onListening);
|
|
38
61
|
attempt.bound = true;
|
|
39
62
|
reportListening(boundPort);
|
|
40
63
|
resolve(boundPort);
|
|
@@ -67,7 +90,7 @@ export function createLoopbackListener({
|
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
server.on('error', onError);
|
|
70
|
-
server.
|
|
93
|
+
server.on('listening', onListening);
|
|
71
94
|
listen();
|
|
72
95
|
return promise;
|
|
73
96
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export function canonicalRouteProvider(value: unknown): string;
|
|
2
2
|
export function routeChain(route: Record<string, unknown>): Record<string, unknown>[];
|
|
3
|
-
export function
|
|
3
|
+
export function executionFallbacks(route: Record<string, unknown> | null | undefined): Record<string, unknown>[];
|
|
4
|
+
export function validateRouteChainProviders(
|
|
5
|
+
routes: Record<string, unknown>[],
|
|
6
|
+
options?: { allowDuplicates?: boolean }
|
|
7
|
+
): void;
|
|
4
8
|
export function normalizeRouteChain(
|
|
5
9
|
route: Record<string, unknown>,
|
|
6
10
|
normalizeLeaf: (route: Record<string, unknown>, fallback: Record<string, unknown>) => Record<string, unknown> | null,
|
|
7
|
-
fallback?: Record<string, unknown
|
|
11
|
+
fallback?: Record<string, unknown>,
|
|
12
|
+
options?: { strict?: boolean }
|
|
8
13
|
): Record<string, unknown> | null;
|
|
@@ -22,18 +22,36 @@ export function routeChain(route) {
|
|
|
22
22
|
return [primary, ...fallbacks];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/** The fallbacks a chain runs: those on a provider not already used by the
|
|
26
|
+
* primary or an earlier fallback. The stored list is never changed; a fallback
|
|
27
|
+
* on the main model's own provider (left there by a later main-model change)
|
|
28
|
+
* is simply inactive. */
|
|
29
|
+
export function executionFallbacks(route) {
|
|
30
|
+
const [primary, ...fallbacks] = routeChain(route);
|
|
31
|
+
if (!primary) return [];
|
|
32
|
+
const seen = new Set([canonicalRouteProvider(primary.provider)]);
|
|
33
|
+
return fallbacks.filter((entry) => {
|
|
34
|
+
const provider = canonicalRouteProvider(entry.provider);
|
|
35
|
+
if (seen.has(provider)) return false;
|
|
36
|
+
seen.add(provider);
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `allowDuplicates` accepts a stored chain whose fallbacks repeat a provider
|
|
42
|
+
* (see executionFallbacks); an explicit save validates without it. */
|
|
43
|
+
export function validateRouteChainProviders(routes, { allowDuplicates = false } = {}) {
|
|
26
44
|
const providers = new Set();
|
|
27
45
|
for (const route of routes) {
|
|
28
46
|
const provider = canonicalRouteProvider(route.provider);
|
|
29
47
|
if (!provider || !String(route.model || '').trim()) throw new Error('route requires provider and model');
|
|
30
|
-
if (providers.has(provider)) throw new Error(`duplicate route provider "${provider}"`);
|
|
48
|
+
if (!allowDuplicates && providers.has(provider)) throw new Error(`duplicate route provider "${provider}"`);
|
|
31
49
|
if (providers.size && provider === 'default') throw new Error('follow Main is only allowed as the primary route');
|
|
32
50
|
providers.add(provider);
|
|
33
51
|
}
|
|
34
52
|
}
|
|
35
53
|
|
|
36
|
-
export function normalizeRouteChain(route, normalizeLeaf, fallback = {}) {
|
|
54
|
+
export function normalizeRouteChain(route, normalizeLeaf, fallback = {}, { strict = true } = {}) {
|
|
37
55
|
const raw = routeChain({ ...fallback, ...route });
|
|
38
56
|
const primary = normalizeLeaf(route, fallback);
|
|
39
57
|
if (!primary) return null;
|
|
@@ -42,7 +60,7 @@ export function normalizeRouteChain(route, normalizeLeaf, fallback = {}) {
|
|
|
42
60
|
if (!normalized) throw new Error('fallback route requires provider and model');
|
|
43
61
|
return { ...normalized, provider: canonicalRouteProvider(normalized.provider) };
|
|
44
62
|
});
|
|
45
|
-
validateRouteChainProviders(routes);
|
|
63
|
+
validateRouteChainProviders(routes, { allowDuplicates: !strict });
|
|
46
64
|
const [first, ...rest] = routes;
|
|
47
65
|
return rest.length ? { ...first, fallbacks: rest } : first;
|
|
48
66
|
}
|
|
@@ -2,6 +2,16 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { isWSL } from './wsl.mjs';
|
|
3
3
|
import { detachedSpawnOpts } from './spawn-flags.mjs';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Whether a browser can plausibly open on this machine: not inside an SSH
|
|
7
|
+
* session, and on non-WSL Linux a DISPLAY or WAYLAND_DISPLAY exists.
|
|
8
|
+
*/
|
|
9
|
+
export function canOpenBrowser(env = process.env, platform = process.platform, wsl = isWSL()) {
|
|
10
|
+
if (env.SSH_TTY || env.SSH_CONNECTION) return false;
|
|
11
|
+
if (platform !== 'linux' || wsl) return true;
|
|
12
|
+
return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* Open a URL in the user's default browser. Best-effort and non-blocking.
|
|
7
17
|
* Callers also print the URL, so opener failure should never block OAuth.
|
|
@@ -45,6 +45,20 @@ export function emitSessionApprovalCardChange(sessionId = '') {
|
|
|
45
45
|
emit(approvalCardListeners, sessionId);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// The bots whose sends are in flight in a session changed (bot-owned-work.mjs):
|
|
49
|
+
// the session catalog stamps them on each row (`handedBy`) and re-reads.
|
|
50
|
+
const botWorkListeners = new Set();
|
|
51
|
+
|
|
52
|
+
export function onSessionBotWorkChange(listener) {
|
|
53
|
+
if (typeof listener !== 'function') return () => {};
|
|
54
|
+
botWorkListeners.add(listener);
|
|
55
|
+
return () => botWorkListeners.delete(listener);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function emitSessionBotWorkChange(sessionId = '') {
|
|
59
|
+
emit(botWorkListeners, sessionId);
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
function emit(targets, sessionId) {
|
|
49
63
|
for (const listener of [...targets]) {
|
|
50
64
|
try {
|