mixdog 1.3.4 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +15 -0
- package/README.md +11 -0
- package/package.json +2 -1
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/test.mjs +5 -0
- package/src/defaults/shipped-history.json +8 -0
- package/src/defaults/skills/browser-use/SKILL.md +21 -9
- package/src/defaults/skills/setup/SKILL.md +7 -2
- package/src/defaults/skills/setup/references/actions.md +82 -27
- package/src/defaults/skills/setup/references/surfaces.md +67 -34
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +109 -14
- package/src/runtime/channels/lib/schedules-db.mjs +49 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
- package/src/runtime/channels/lib/worker-main.mjs +9 -0
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/config.mjs +49 -22
- package/src/runtime/shared/connector-catalog.d.mts +4 -2
- package/src/runtime/shared/connector-catalog.mjs +16 -8
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/provider-accounts.mjs +224 -58
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/routine-trigger.mjs +42 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/session-runtime/agent-home-delivery.mjs +57 -13
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +292 -79
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
- package/src/session-runtime/mixy-heartbeat.mjs +807 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/owned-automation-run.mjs +33 -12
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/channel-admin.mjs +66 -18
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -4
- package/src/session-runtime/settings-system-api.mjs +16 -1
- package/src/session-runtime/setup-tool/executor.mjs +49 -14
- package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
- package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
- package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/daemon-session-control.mjs +88 -20
- package/src/standalone/daemon.mjs +8 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +8 -5
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/dist/index.mjs +24 -2
- package/src/tui/session/notice-surface.mjs +17 -1
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/settings.mjs +14 -2
- package/src/tui/session/turn-stream.mjs +17 -1
- package/src/tui/session/turn.mjs +22 -2
|
@@ -37,7 +37,11 @@ import {
|
|
|
37
37
|
isTranscriptCancelledStatusText,
|
|
38
38
|
} from '../../../../shared/tool-execution-contract.mjs';
|
|
39
39
|
import { toolResultTerminalStatus } from '../../../../shared/tool-status.mjs';
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
transcriptAttachmentFields,
|
|
42
|
+
transcriptReactionsField,
|
|
43
|
+
transcriptRouteMetadataFields,
|
|
44
|
+
} from '../../../../shared/transcript-metadata.mjs';
|
|
41
45
|
import { routineTriggerField } from '../../../../shared/routine-trigger.mjs';
|
|
42
46
|
import { isImportedMessage } from '../imported-message.mjs';
|
|
43
47
|
import { isContextAckMessage } from '../compact/user-message-kind.mjs';
|
|
@@ -91,6 +95,8 @@ function restoredTranscriptMetadata(message) {
|
|
|
91
95
|
// The attachments this prompt carried, by content hash: a restored row
|
|
92
96
|
// opens/downloads them by id from the attachment store.
|
|
93
97
|
...transcriptAttachmentFields(value),
|
|
98
|
+
// The bots' emoji reactions on a user message (message-reactions.mjs).
|
|
99
|
+
...transcriptReactionsField(value),
|
|
94
100
|
// A routine fire this turn delivered: painted as its marker row.
|
|
95
101
|
...routineTriggerField(value),
|
|
96
102
|
// Skills this user turn explicitly loaded (header selection or `$name`).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Structured reasons a schedule rename is refused or ends unsure. The app
|
|
2
|
+
// decides on the `code` and the `name` detail (the desktop's
|
|
3
|
+
// shared/schedule-error-codes.ts mirrors these codes), never on the English
|
|
4
|
+
// message, which is for the model and the logs.
|
|
5
|
+
|
|
6
|
+
/** Another schedule already has the name (detail: `name`, the name asked for). */
|
|
7
|
+
export const SCHEDULE_NAME_TAKEN = 'SCHEDULE_NAME_TAKEN';
|
|
8
|
+
/** A run of the schedule is in flight, or another rename of it (detail: `name`). */
|
|
9
|
+
export const SCHEDULE_RENAME_RUNNING = 'SCHEDULE_RENAME_RUNNING';
|
|
10
|
+
/** The row moved but the scheduler did not reload (detail: `name`, the new name). */
|
|
11
|
+
export const SCHEDULE_RENAME_RELOAD_FAILED = 'SCHEDULE_RENAME_RELOAD_FAILED';
|
|
12
|
+
/** The scheduler never answered, so the rename may or may not have applied. */
|
|
13
|
+
export const SCHEDULE_RENAME_UNCERTAIN = 'SCHEDULE_RENAME_UNCERTAIN';
|
|
14
|
+
|
|
15
|
+
const NAMED_CODES = new Set([SCHEDULE_NAME_TAKEN, SCHEDULE_RENAME_RUNNING, SCHEDULE_RENAME_RELOAD_FAILED]);
|
|
16
|
+
const CODES = new Set([...NAMED_CODES, SCHEDULE_RENAME_UNCERTAIN]);
|
|
17
|
+
|
|
18
|
+
/** The fields of `error` a reply may carry across the worker boundary: a
|
|
19
|
+
* schedule code and, for the codes that name a schedule, that public `name`.
|
|
20
|
+
* Any other code or detail stays on this side. */
|
|
21
|
+
export function scheduleErrorFields(error) {
|
|
22
|
+
const code = error?.code;
|
|
23
|
+
if (typeof code !== 'string' || !CODES.has(code)) return {};
|
|
24
|
+
const name = error.details?.name;
|
|
25
|
+
return {
|
|
26
|
+
code,
|
|
27
|
+
...(NAMED_CODES.has(code) && typeof name === 'string' && name ? { details: { name } } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const coded = (message, code, details = null) =>
|
|
32
|
+
Object.assign(new Error(message), { code }, details ? { details } : {});
|
|
33
|
+
|
|
34
|
+
export const scheduleNameTakenError = (name) =>
|
|
35
|
+
coded(`A schedule named "${name}" already exists.`, SCHEDULE_NAME_TAKEN, { name });
|
|
36
|
+
|
|
37
|
+
export const scheduleRenameRunningError = (name) =>
|
|
38
|
+
coded(`Schedule "${name}" is running. Rename it after the run finishes.`, SCHEDULE_RENAME_RUNNING, { name });
|
|
39
|
+
|
|
40
|
+
export const scheduleRenameReloadError = (name, cause) =>
|
|
41
|
+
coded(
|
|
42
|
+
`Renamed to "${name}", but reloading the scheduler failed (${cause}); it is armed on the next reload.`,
|
|
43
|
+
SCHEDULE_RENAME_RELOAD_FAILED,
|
|
44
|
+
{ name }
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export const scheduleRenameUncertainError = (cause) =>
|
|
48
|
+
coded(
|
|
49
|
+
`The rename may or may not have applied (the scheduler did not answer: ${cause}). Check the schedule list.`,
|
|
50
|
+
SCHEDULE_RENAME_UNCERTAIN
|
|
51
|
+
);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
setNextFire,
|
|
19
19
|
setSkippedUntil,
|
|
20
20
|
} from './schedules-db.mjs';
|
|
21
|
+
import { scheduleRenameReloadError, scheduleRenameRunningError } from './schedule-errors.mjs';
|
|
21
22
|
import { appendBuffered } from '../../shared/buffered-appender.mjs';
|
|
22
23
|
import { tryRead } from './settings.mjs';
|
|
23
24
|
|
|
@@ -121,6 +122,8 @@ class Scheduler {
|
|
|
121
122
|
lastFired = /* @__PURE__ */ new Map();
|
|
122
123
|
// name -> "YYYY-MM-DDTHH:MM"
|
|
123
124
|
running = /* @__PURE__ */ new Set();
|
|
125
|
+
// Names whose row is being renamed: no fire starts under them meanwhile.
|
|
126
|
+
renaming = /* @__PURE__ */ new Set();
|
|
124
127
|
injectFn = null;
|
|
125
128
|
injectReadyFn = null;
|
|
126
129
|
pendingCheck = null;
|
|
@@ -415,6 +418,12 @@ ${prompt}`;
|
|
|
415
418
|
}
|
|
416
419
|
return;
|
|
417
420
|
}
|
|
421
|
+
// The name is held in `running` from before the claim until the completion
|
|
422
|
+
// stamps are written, so a rename can neither complete meanwhile nor leave
|
|
423
|
+
// this fire to run under the old name. A fire refused here still claims its
|
|
424
|
+
// slot and is recorded as a skip, never run.
|
|
425
|
+
const refusal = this.fireRefusal(schedule.name);
|
|
426
|
+
if (!refusal) this.running.add(schedule.name);
|
|
418
427
|
try {
|
|
419
428
|
const claimed = await claimScheduleRun(schedule.name, scheduledAt, startedAt, nextFireAt);
|
|
420
429
|
if (!claimed) {
|
|
@@ -425,7 +434,11 @@ ${prompt}`;
|
|
|
425
434
|
schedule.lastStartedAt = startedAt.toISOString();
|
|
426
435
|
schedule.lastFiredAt = startedAt.toISOString();
|
|
427
436
|
this.lastFired.set(schedule.name, startedAt.toISOString());
|
|
428
|
-
|
|
437
|
+
if (refusal) {
|
|
438
|
+
this.recordRefusal(schedule, refusal);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const fired = await this.fireTimed(schedule, type, { awaitDispatch: true, reserved: true });
|
|
429
442
|
// An overlap skip is recorded as a skip, never as a failed run.
|
|
430
443
|
if (!fired && skippedSince(schedule, startedAt)) return;
|
|
431
444
|
if (!fired) {
|
|
@@ -443,6 +456,8 @@ ${prompt}`;
|
|
|
443
456
|
} catch {}
|
|
444
457
|
process.stderr.write(`mixdog scheduler: ${schedule.name} failed: ${err}\n`);
|
|
445
458
|
this.notifyFailure(schedule, `run failed: ${err?.message || err}`);
|
|
459
|
+
} finally {
|
|
460
|
+
if (!refusal) this.running.delete(schedule.name);
|
|
446
461
|
}
|
|
447
462
|
}
|
|
448
463
|
stop() {
|
|
@@ -521,12 +536,17 @@ ${prompt}`;
|
|
|
521
536
|
// Mark done ONLY after a successful fire. A false/throwing fireTimed
|
|
522
537
|
// (missing prompt, running guard, paused owner, dispatch error) must leave the
|
|
523
538
|
// one-shot pending so a reload/restart can retry it — never retire it.
|
|
539
|
+
// The name stays in `running` until the fired/done stamps are written, so
|
|
540
|
+
// a rename cannot move the row before they land (onCronFire). A refused
|
|
541
|
+
// fire is left to fireTimedPrompt, which records the skip.
|
|
542
|
+
const reserved = !this.fireRefusal(schedule.name);
|
|
543
|
+
if (reserved) this.running.add(schedule.name);
|
|
524
544
|
try {
|
|
525
545
|
// awaitDispatch: for a one-shot we must wait for the actual LLM/relay
|
|
526
546
|
// dispatch outcome — a truthy fireTimed alone (cron's fire-and-forget
|
|
527
547
|
// contract) would retire the entry even when schedulerLlm() later
|
|
528
548
|
// rejects. Only a resolved dispatch counts as a real fire here.
|
|
529
|
-
const fired = await this.fireTimed(schedule, type, { awaitDispatch: true });
|
|
549
|
+
const fired = await this.fireTimed(schedule, type, { awaitDispatch: true, reserved });
|
|
530
550
|
if (!fired && skippedSince(schedule, now)) {
|
|
531
551
|
logSchedule(`one-shot "${schedule.name}" skipped (previous run in progress) — leaving pending for retry\n`);
|
|
532
552
|
return;
|
|
@@ -542,6 +562,8 @@ ${prompt}`;
|
|
|
542
562
|
await markOneShotFailed(schedule);
|
|
543
563
|
process.stderr.write(`mixdog scheduler: ${schedule.name} one-shot failed: ${err} — leaving pending for retry\n`);
|
|
544
564
|
this.notifyFailure(schedule, `one-shot failed: ${err?.message || err} — left pending for retry`);
|
|
565
|
+
} finally {
|
|
566
|
+
if (reserved) this.running.delete(schedule.name);
|
|
545
567
|
}
|
|
546
568
|
}
|
|
547
569
|
/** when_at one-shots must honor deferred_until / skipped_until just like
|
|
@@ -577,10 +599,60 @@ ${prompt}`;
|
|
|
577
599
|
releaseSchedulerRuntime(this);
|
|
578
600
|
this.start();
|
|
579
601
|
}
|
|
602
|
+
/** Run `rename` (which moves schedule `name`'s row) while no run of `name`
|
|
603
|
+
* can start or be in flight. The check and the reservation are one
|
|
604
|
+
* synchronous step, and a fire checks for the reservation (fireRefusal)
|
|
605
|
+
* in the same step that adds the name to `running`, which it holds until
|
|
606
|
+
* its completion stamps are written, so a fire either is already in
|
|
607
|
+
* `running` (refused here) or finds the reservation (not fired). */
|
|
608
|
+
async renameLocked(name, rename, { newName = name, reload = async () => {} } = {}) {
|
|
609
|
+
if (this.running.has(name) || this.renaming.has(name)) throw scheduleRenameRunningError(name);
|
|
610
|
+
this.renaming.add(name);
|
|
611
|
+
this.renameBlocked?.delete(name);
|
|
612
|
+
try {
|
|
613
|
+
const saved = await rename();
|
|
614
|
+
if (!saved) return saved;
|
|
615
|
+
try {
|
|
616
|
+
await reload();
|
|
617
|
+
} catch (error) {
|
|
618
|
+
// The row has moved but the reload that arms the new name failed:
|
|
619
|
+
// nothing may keep running under the old one, and the call reports it.
|
|
620
|
+
this.dropSchedule(name);
|
|
621
|
+
throw scheduleRenameReloadError(newName, error?.message || error);
|
|
622
|
+
}
|
|
623
|
+
return saved;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
// A one-shot whose timer a blocked fire consumed is armed again from the
|
|
626
|
+
// loaded schedules, so it fires once (after a failed rename the old
|
|
627
|
+
// entry is still loaded; after a dropped one only the others re-arm).
|
|
628
|
+
if (this.renameBlocked?.has(name) && this.tickTimer) this.restart();
|
|
629
|
+
throw error;
|
|
630
|
+
} finally {
|
|
631
|
+
this.renaming.delete(name);
|
|
632
|
+
this.renameBlocked?.delete(name);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/** Forget `name` entirely: its cron job, one-shot timer, stamps and loaded
|
|
636
|
+
* entries. Used when its row moved but the reload that re-arms it failed. */
|
|
637
|
+
dropSchedule(name) {
|
|
638
|
+
try {
|
|
639
|
+
this.cronJobs.get(name)?.destroy();
|
|
640
|
+
} catch {}
|
|
641
|
+
this.cronJobs.delete(name);
|
|
642
|
+
clearTimeout(this.oneShotTimers.get(name));
|
|
643
|
+
this.oneShotTimers.delete(name);
|
|
644
|
+
this.lastFired.delete(name);
|
|
645
|
+
this.deferred.delete(name);
|
|
646
|
+
this.nonInteractive = this.nonInteractive.filter((s) => s.name !== name);
|
|
647
|
+
this.interactive = this.interactive.filter((s) => s.name !== name);
|
|
648
|
+
}
|
|
580
649
|
reloadConfig(nonInteractive, interactive, options = {}) {
|
|
581
650
|
this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
|
|
582
651
|
this.interactive = interactive.filter((s) => s.enabled !== false);
|
|
583
652
|
this.promptsDir = join(DATA_DIR, 'prompts');
|
|
653
|
+
// A name that no longer exists must not keep (or later regain) a stamp.
|
|
654
|
+
const live = new Set([...this.nonInteractive, ...this.interactive].map((s) => s.name));
|
|
655
|
+
for (const name of this.lastFired.keys()) if (!live.has(name)) this.lastFired.delete(name);
|
|
584
656
|
// Defer/skip state is persisted (deferred_until / skipped_until) and
|
|
585
657
|
// re-read from the reloaded rows, so a reload no longer drops it.
|
|
586
658
|
this.refreshSkipCache();
|
|
@@ -646,20 +718,42 @@ ${prompt}`;
|
|
|
646
718
|
}
|
|
647
719
|
return await this.fireTimedPrompt(schedule, type, prompt, opts);
|
|
648
720
|
}
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
721
|
+
/** Why a fire of `name` may not start now: 'renaming', 'running' or null. */
|
|
722
|
+
fireRefusal(name) {
|
|
723
|
+
if (this.renaming.has(name)) return 'renaming';
|
|
724
|
+
return this.running.has(name) ? 'running' : null;
|
|
725
|
+
}
|
|
726
|
+
/** Record a fire refused for `reason` (fireRefusal) as a skip. */
|
|
727
|
+
recordRefusal(schedule, reason) {
|
|
728
|
+
if (reason === 'running') {
|
|
653
729
|
this.recordSkip(schedule);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
logSchedule(`${schedule.name}: not fired — being renamed\n`);
|
|
733
|
+
// A skip, not a failure: a one-shot stays pending (fireOneShot) and
|
|
734
|
+
// renameLocked re-arms it if the rename does not go through.
|
|
735
|
+
schedule.lastSkippedAt = new Date().toISOString();
|
|
736
|
+
// Per name: another rename settling meanwhile must not hide this one.
|
|
737
|
+
this.renameBlocked ??= new Set();
|
|
738
|
+
this.renameBlocked.add(schedule.name);
|
|
739
|
+
}
|
|
740
|
+
/** Fire a timed schedule with the given prompt content. `reserved`: the
|
|
741
|
+
* caller already holds the name in `running` and releases it itself. */
|
|
742
|
+
async fireTimedPrompt(schedule, type, prompt, { awaitDispatch = false, reserved = false } = {}) {
|
|
743
|
+
logSchedule(`firing ${schedule.name} (${type})\n`);
|
|
744
|
+
const refusal = reserved ? null : this.fireRefusal(schedule.name);
|
|
745
|
+
if (refusal) {
|
|
746
|
+
this.recordRefusal(schedule, refusal);
|
|
654
747
|
return false;
|
|
655
748
|
}
|
|
656
749
|
if (type === 'interactive' && this.injectIntoLead(schedule, type, prompt)) return true;
|
|
657
|
-
this.running.add(schedule.name);
|
|
750
|
+
if (!reserved) this.running.add(schedule.name);
|
|
658
751
|
// Cron (recurring) keeps the fire-and-forget contract: return truthy now
|
|
659
752
|
// and swallow async failures. A one-shot (awaitDispatch) instead awaits
|
|
660
753
|
// the dispatch so a rejected run propagates and the caller leaves the
|
|
661
754
|
// entry pending for retry instead of retiring it.
|
|
662
|
-
const
|
|
755
|
+
const run = this.dispatchScheduleRun(schedule, prompt, awaitDispatch);
|
|
756
|
+
const dispatch = reserved ? run : run.finally(() => this.running.delete(schedule.name));
|
|
663
757
|
if (awaitDispatch) return await dispatch;
|
|
664
758
|
return true;
|
|
665
759
|
}
|
|
@@ -688,12 +782,14 @@ ${prompt}`;
|
|
|
688
782
|
* callers and resolves false (after a logged notice) for fire-and-forget
|
|
689
783
|
* cron fires. */
|
|
690
784
|
dispatchScheduleRun(schedule, prompt, awaitDispatch) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
785
|
+
// Called inside a then, so a runner that throws synchronously rejects
|
|
786
|
+
// like one that rejects, and the caller's cleanup still runs.
|
|
787
|
+
return Promise.resolve()
|
|
788
|
+
.then(() => {
|
|
789
|
+
if (!this.scheduleRunner) throw new Error('schedule runner not configured');
|
|
790
|
+
return this.scheduleRunner(schedule, { prompt });
|
|
791
|
+
})
|
|
695
792
|
.then((outcome) => {
|
|
696
|
-
this.running.delete(schedule.name);
|
|
697
793
|
// The run path found this schedule's previous run still going (a
|
|
698
794
|
// "run now" from the app) or backing off after failures in a row; it
|
|
699
795
|
// already persisted the skip.
|
|
@@ -706,7 +802,6 @@ ${prompt}`;
|
|
|
706
802
|
return true;
|
|
707
803
|
})
|
|
708
804
|
.catch((err) => {
|
|
709
|
-
this.running.delete(schedule.name);
|
|
710
805
|
logSchedule(`${schedule.name} LLM error: ${err.message}\n`);
|
|
711
806
|
// The cron fire-and-forget contract swallowed failures entirely;
|
|
712
807
|
// log them so a failed scheduled run is never
|
|
@@ -15,6 +15,7 @@ import { ensurePgInstance, withSchemaBootstrapLock } from '../../memory/lib/pg/a
|
|
|
15
15
|
import { createPgSchemaDb } from '../../shared/pg-schema-db.mjs';
|
|
16
16
|
import { resolvePluginData } from '../../shared/plugin-paths.mjs';
|
|
17
17
|
import { ROUTINE_LIMITS } from '../../shared/routine-limits.mjs';
|
|
18
|
+
import { scheduleNameTakenError } from './schedule-errors.mjs';
|
|
18
19
|
|
|
19
20
|
const SCHEMA = 'scheduler';
|
|
20
21
|
|
|
@@ -195,6 +196,54 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
195
196
|
return rowToDef(rows[0]);
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Rename schedule `oldName` to `def.name` in one UPDATE of the primary key,
|
|
201
|
+
* applying the edited settings. Run history (last_* stamps, failure streak,
|
|
202
|
+
* undelivered results), status and the next_fire_at cursor (a deferral, or
|
|
203
|
+
* none for a finished one-shot) stay on the row. When `timingChanged`, the row
|
|
204
|
+
* is re-armed the way `upsertSchedule` re-arms a redefinition: a one-shot's
|
|
205
|
+
* cursor becomes its `when_at`, a recurring one's is cleared for the scheduler
|
|
206
|
+
* to compute. Resolves null when `oldName` is gone; an occupied `def.name`
|
|
207
|
+
* rejects and changes nothing.
|
|
208
|
+
*/
|
|
209
|
+
export async function renameSchedule(oldName, def, { timingChanged = false, dataDir } = {}) {
|
|
210
|
+
const db = await getDb(dataDir);
|
|
211
|
+
try {
|
|
212
|
+
const { rows } = await db.query(
|
|
213
|
+
`UPDATE scheduler.schedules SET
|
|
214
|
+
name = $2, description = $3, when_at = $4, when_cron = $5, timezone = $6, cwd = $7,
|
|
215
|
+
attachments = $8, prompt = $9, enabled = $10, owner_agent_id = $11,
|
|
216
|
+
next_fire_at = CASE WHEN $12::boolean THEN $4::timestamptz ELSE next_fire_at END,
|
|
217
|
+
status = CASE WHEN $12::boolean THEN 'active' ELSE status END,
|
|
218
|
+
deferred_until = CASE WHEN $12::boolean THEN NULL ELSE deferred_until END,
|
|
219
|
+
skipped_until = CASE WHEN $12::boolean THEN NULL ELSE skipped_until END,
|
|
220
|
+
last_fired_at = CASE WHEN $12::boolean THEN NULL ELSE last_fired_at END,
|
|
221
|
+
last_scheduled_at = CASE WHEN $12::boolean THEN NULL ELSE last_scheduled_at END,
|
|
222
|
+
updated_at = now()
|
|
223
|
+
WHERE name = $1
|
|
224
|
+
RETURNING ${COLS}`,
|
|
225
|
+
[
|
|
226
|
+
oldName,
|
|
227
|
+
def.name,
|
|
228
|
+
def.description ?? '',
|
|
229
|
+
def.whenAt ?? null,
|
|
230
|
+
def.whenCron ?? null,
|
|
231
|
+
def.timezone ?? null,
|
|
232
|
+
def.cwd ?? null,
|
|
233
|
+
def.attachments ? JSON.stringify(def.attachments) : null,
|
|
234
|
+
def.prompt,
|
|
235
|
+
def.enabled ?? true,
|
|
236
|
+
def.ownerAgentId || 'lead',
|
|
237
|
+
timingChanged === true,
|
|
238
|
+
]
|
|
239
|
+
);
|
|
240
|
+
return rowToDef(rows[0]);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error?.code === '23505') throw scheduleNameTakenError(def.name);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
198
247
|
export async function deleteSchedule(name, { dataDir } = {}) {
|
|
199
248
|
const db = await getDb(dataDir);
|
|
200
249
|
const { rowCount } = await db.query(`DELETE FROM scheduler.schedules WHERE name = $1`, [name]);
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// is deleted with Discord/Telegram; only the automation lifecycle remains.
|
|
4
4
|
// Lifecycle functions are threaded in as a bag of lazy getters so the module
|
|
5
5
|
// reads the live file-level references at call time.
|
|
6
|
+
import { scheduleErrorFields } from './schedule-errors.mjs';
|
|
7
|
+
|
|
6
8
|
function createToolDispatch({ isChannelsDegraded, lifecycle }) {
|
|
7
9
|
const {
|
|
8
10
|
getChannelBridgeActive,
|
|
@@ -13,6 +15,7 @@ function createToolDispatch({ isChannelsDegraded, lifecycle }) {
|
|
|
13
15
|
startChannelBridge,
|
|
14
16
|
stopOwnedRuntime,
|
|
15
17
|
reloadRuntimeConfig,
|
|
18
|
+
renameSchedule,
|
|
16
19
|
} = lifecycle;
|
|
17
20
|
|
|
18
21
|
async function handleToolCall(name, args, _signal) {
|
|
@@ -82,6 +85,14 @@ function createToolDispatch({ isChannelsDegraded, lifecycle }) {
|
|
|
82
85
|
};
|
|
83
86
|
break;
|
|
84
87
|
}
|
|
88
|
+
// The scheduler's owner moves a schedule row only while no run of it
|
|
89
|
+
// can start or be in flight (Scheduler.renameLocked). Resolves the
|
|
90
|
+
// renamed row as JSON text; a refusal is an isError result.
|
|
91
|
+
case 'rename_schedule': {
|
|
92
|
+
const saved = await renameSchedule(args.name, args.def, { timingChanged: args.timingChanged === true });
|
|
93
|
+
result = { content: [{ type: 'text', text: JSON.stringify(saved) }] };
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
85
96
|
// memory — handled by memory-service.mjs MCP
|
|
86
97
|
default:
|
|
87
98
|
result = {
|
|
@@ -94,6 +105,8 @@ function createToolDispatch({ isChannelsDegraded, lifecycle }) {
|
|
|
94
105
|
result = {
|
|
95
106
|
content: [{ type: 'text', text: `${name} failed: ${msg}` }],
|
|
96
107
|
isError: true,
|
|
108
|
+
// A schedule refusal keeps its code and public name; nothing else crosses.
|
|
109
|
+
...scheduleErrorFields(err),
|
|
97
110
|
};
|
|
98
111
|
}
|
|
99
112
|
return result;
|
|
@@ -7,6 +7,7 @@ import { whisperThreadCount } from './voice-transcription.mjs';
|
|
|
7
7
|
import { ensureReady, stopVoiceWhisperServer } from './whisper-server.mjs';
|
|
8
8
|
import { ensurePrivateRuntimeRoot, resolveRuntimeRoot } from '../../shared/runtime-root.mjs';
|
|
9
9
|
import { Scheduler } from './scheduler.mjs';
|
|
10
|
+
import { renameSchedule as renameScheduleRow } from './schedules-db.mjs';
|
|
10
11
|
import { hasPending as dispatchHasPending } from '../../agent/orchestrator/dispatch-persist.mjs';
|
|
11
12
|
import { stripSoftWarns } from '../../agent/orchestrator/tool-loop-guard.mjs';
|
|
12
13
|
import { JsonStateFile } from './state-file.mjs';
|
|
@@ -264,6 +265,14 @@ const { handleToolCall } = createToolDispatch({
|
|
|
264
265
|
startChannelBridge: () => start({ messaging: true }),
|
|
265
266
|
stopOwnedRuntime,
|
|
266
267
|
reloadRuntimeConfig,
|
|
268
|
+
// Rename and re-arm under one reservation: the scheduler is reloaded from
|
|
269
|
+
// the renamed row (timers and one-shots of the old name cleared) before
|
|
270
|
+
// the name is released for fires.
|
|
271
|
+
renameSchedule: (name, def, opts) =>
|
|
272
|
+
scheduler.renameLocked(name, () => renameScheduleRow(name, def, opts), {
|
|
273
|
+
newName: def.name,
|
|
274
|
+
reload: reloadRuntimeConfig,
|
|
275
|
+
}),
|
|
267
276
|
},
|
|
268
277
|
});
|
|
269
278
|
function ensureConfigWatcher() {
|
|
@@ -39,6 +39,15 @@ function spawnPowerShell(script) {
|
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Resolves once the host process is gone. A signal-terminated child has exitCode null but signalCode set,
|
|
43
|
+
// and its 'close' may already have been emitted, so both are checked before waiting for the event.
|
|
44
|
+
export function hostExitSignal(child) {
|
|
45
|
+
return new Promise((done) => {
|
|
46
|
+
if (child.exitCode !== null || child.signalCode !== null) done();
|
|
47
|
+
else child.once('close', () => done());
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
async function callMicrosoftOfficeOnce(payload, { timeoutMs = DEFAULT_TIMEOUT_MS, signal = null } = {}) {
|
|
43
52
|
return await new Promise((resolve) => {
|
|
44
53
|
const child = spawnPowerShell(HOST_SCRIPT);
|
|
@@ -65,13 +74,20 @@ async function callMicrosoftOfficeOnce(payload, { timeoutMs = DEFAULT_TIMEOUT_MS
|
|
|
65
74
|
},
|
|
66
75
|
};
|
|
67
76
|
void drainSessionClient(client, error).then((cleanup) => {
|
|
68
|
-
|
|
77
|
+
const result = {
|
|
69
78
|
ok: false,
|
|
70
79
|
cancelled,
|
|
71
80
|
error: officeCleanupError(error, cleanup),
|
|
72
81
|
operationMayHaveCompleted: true,
|
|
73
82
|
cleanup,
|
|
83
|
+
};
|
|
84
|
+
// Settles when the host process is gone, which may be after this result when the
|
|
85
|
+
// grace period lapsed: callers that own files the host holds open wait on it.
|
|
86
|
+
// Non-enumerable so the result still serializes unchanged.
|
|
87
|
+
Object.defineProperty(result, 'hostExit', {
|
|
88
|
+
value: hostExitSignal(child),
|
|
74
89
|
});
|
|
90
|
+
finish(result);
|
|
75
91
|
});
|
|
76
92
|
};
|
|
77
93
|
const onAbort = () => beginCleanup('Microsoft Office operation was cancelled', true);
|
|
@@ -7545,6 +7545,52 @@ function Open-ValidationDocument($app, [string]$format, [string]$path) {
|
|
|
7545
7545
|
}
|
|
7546
7546
|
}
|
|
7547
7547
|
|
|
7548
|
+
$script:PreviewPasswordMessage = 'This file is password-protected. (OFFICE_PREVIEW_PASSWORD_PROTECTED)'
|
|
7549
|
+
|
|
7550
|
+
# An encrypted Office Open XML package is an OLE container (D0 CF 11 E0), not a ZIP. Office would stop to ask
|
|
7551
|
+
# for the password, and a hidden application cannot show the question, so the request is refused up front.
|
|
7552
|
+
function Assert-PreviewNotEncrypted([string]$path) {
|
|
7553
|
+
$extension = [System.IO.Path]::GetExtension($path).TrimStart('.').ToLowerInvariant()
|
|
7554
|
+
if (@('docx', 'docm', 'dotx', 'xlsx', 'xlsm', 'pptx', 'pptm') -notcontains $extension) { return }
|
|
7555
|
+
$header = New-Object byte[] 4
|
|
7556
|
+
$stream = [System.IO.File]::Open($path, 'Open', 'Read', 'ReadWrite')
|
|
7557
|
+
try { $read = $stream.Read($header, 0, 4) } finally { $stream.Dispose() }
|
|
7558
|
+
if ($read -eq 4 -and $header[0] -eq 0xD0 -and $header[1] -eq 0xCF -and $header[2] -eq 0x11 -and $header[3] -eq 0xE0) {
|
|
7559
|
+
throw $script:PreviewPasswordMessage
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
|
|
7563
|
+
$script:PreviewWrongPasswordMessage = "That password didn't work. (OFFICE_PREVIEW_WRONG_PASSWORD)"
|
|
7564
|
+
|
|
7565
|
+
# The read-only open of the file preview. Word and Excel get the user's password when the request carries one
|
|
7566
|
+
# (Documents.Open PasswordDocument, Workbooks.Open Password), else a placeholder, so a protected legacy file fails
|
|
7567
|
+
# at once instead of waiting on a prompt nobody can see. Word's "password is incorrect" (0x800A1520) becomes the
|
|
7568
|
+
# plain message: the protected one without a password, the wrong-password one with it. Presentations.Open takes no
|
|
7569
|
+
# password, so PowerPoint never gets one. Links are not updated, macros stay off and DisplayAlerts is off
|
|
7570
|
+
# (New-HiddenApplication). Automation opens do not go through Protected View.
|
|
7571
|
+
function Open-PreviewDocument($app, [string]$format, [string]$path, [string]$password) {
|
|
7572
|
+
$placeholder = 'mixdog-preview-no-password'
|
|
7573
|
+
$openPassword = if ($password) { $password } else { $placeholder }
|
|
7574
|
+
try {
|
|
7575
|
+
switch ($format) {
|
|
7576
|
+
'docx' { return $app.Documents.Open($path, $false, $true, $false, $openPassword, $placeholder, $false, $placeholder, $placeholder) }
|
|
7577
|
+
# No WriteResPassword: Excel rejects one for a workbook without write reservation (0x800A03EC), and a
|
|
7578
|
+
# read-only open never asks for it.
|
|
7579
|
+
'xlsx' { return $app.Workbooks.Open($path, 0, $true, [Type]::Missing, $openPassword, [Type]::Missing, $true) }
|
|
7580
|
+
'pptx' { return $app.Presentations.Open($path, $true, $true, $false) }
|
|
7581
|
+
}
|
|
7582
|
+
}
|
|
7583
|
+
catch {
|
|
7584
|
+
$inner = $_.Exception
|
|
7585
|
+
while ($null -ne $inner.InnerException) { $inner = $inner.InnerException }
|
|
7586
|
+
if ($format -eq 'docx' -and $inner.HResult -eq 0x800A1520) {
|
|
7587
|
+
if ($password) { throw $script:PreviewWrongPasswordMessage }
|
|
7588
|
+
throw $script:PreviewPasswordMessage
|
|
7589
|
+
}
|
|
7590
|
+
throw
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
|
|
7548
7594
|
function Validate-NativeDocument([string]$path, [string]$format) {
|
|
7549
7595
|
$validationApp = $null
|
|
7550
7596
|
$validationDocument = $null
|
|
@@ -7757,9 +7803,13 @@ try {
|
|
|
7757
7803
|
$live = $true
|
|
7758
7804
|
}
|
|
7759
7805
|
else {
|
|
7806
|
+
if ($payload.readOnly -eq $true) { Assert-PreviewNotEncrypted $path }
|
|
7760
7807
|
$app = New-HiddenApplication $format $progId
|
|
7761
7808
|
$createdApp = $true
|
|
7762
|
-
|
|
7809
|
+
# A read-only request (the file preview) opens the document read-only, with no link updates and no
|
|
7810
|
+
# recalculation, and never waits on a password prompt. The close below discards, so nothing can be
|
|
7811
|
+
# written back.
|
|
7812
|
+
$document = if ($payload.readOnly -eq $true) { Open-PreviewDocument $app $format $path ([string]$payload.password) } else { Open-BackgroundDocument $app $format $path }
|
|
7763
7813
|
}
|
|
7764
7814
|
|
|
7765
7815
|
switch ([string]$payload.action) {
|