mixdog 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +52 -9
- package/package.json +1 -1
- package/src/defaults/shipped-history.json +4 -1
- package/src/defaults/skills/setup/references/actions.md +4 -1
- package/src/defaults/skills/setup-guide/SKILL.md +10 -7
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent-mode/AGENT_MODE.md +1 -1
- package/src/rules/agent-mode/MIXY.md +11 -4
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +72 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -2
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +14 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +37 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +17 -8
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +8 -14
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +7 -20
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +17 -1
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +61 -9
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +14 -9
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +1 -0
- package/src/runtime/shared/agent-route-config.mjs +22 -8
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/connector-catalog.d.mts +1 -2
- package/src/runtime/shared/connector-catalog.mjs +2 -4
- package/src/runtime/shared/connector-triggers.mjs +2 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.mjs +22 -17
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/usage-readout-totals.mjs +15 -13
- package/src/runtime/shared/user-data-guard.mjs +5 -2
- package/src/session-runtime/agent-home-delivery.mjs +423 -56
- package/src/session-runtime/agent-room-delivery.mjs +45 -4
- package/src/session-runtime/agent-task-sessions.mjs +299 -39
- package/src/session-runtime/boot/apis.mjs +13 -0
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +27 -3
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +21 -1
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/setup-tool/executor.mjs +12 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
- package/src/session-runtime/setup-tool/settings-contract.mjs +6 -0
- package/src/session-runtime/tool-policy-refresh.mjs +13 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +11 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +123 -32
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/standalone/daemon-session-control.mjs +13 -5
- package/src/standalone/daemon.mjs +16 -4
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
- package/src/tui/dist/index.mjs +70 -9
- package/src/tui/session/notice-surface.mjs +3 -4
- package/src/tui/session/session-api/routes.mjs +14 -3
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn-live-call.mjs +11 -3
- package/src/tui/session/turn.mjs +13 -3
|
@@ -10,7 +10,13 @@ export function createRouteTuning(deps, persist) {
|
|
|
10
10
|
const { getConfig, getRoute, setRouteState, resolveRoute, lookupModelMeta, refreshRouteEffort } = deps;
|
|
11
11
|
|
|
12
12
|
/** Save the live route's modelSettings, re-resolve through the lead preset
|
|
13
|
-
* when one exists, refresh effort and tune the current session.
|
|
13
|
+
* when one exists, refresh effort and tune the current session.
|
|
14
|
+
*
|
|
15
|
+
* This is not a Main-model write and needs no compare-and-set: it persists
|
|
16
|
+
* the LIVE route (read after every await), and only when that route's model
|
|
17
|
+
* is already the Main model (persistAdoptedModelSettings: tunesNonMainModel),
|
|
18
|
+
* so the lead preset's model is rewritten with itself and never changes. A
|
|
19
|
+
* live route without a model normalizes to nothing and writes no preset. */
|
|
14
20
|
async function commitRouteTuning(fastCapable, modelMeta) {
|
|
15
21
|
persist.saveRouteModelSettings(getRoute(), fastCapable);
|
|
16
22
|
const leadRoute = persist.persistAdoptedModelSettings(getRoute());
|
|
@@ -195,9 +195,23 @@ async function adoptRoute(deps, persist, next, options, lease) {
|
|
|
195
195
|
scheduleStatuslineUsageRefresh,
|
|
196
196
|
} = deps;
|
|
197
197
|
const { selectedRoute, fastCapable, modelMeta } = await resolveSelectedRoute(deps, next);
|
|
198
|
+
// The Lead route a conditional fill wrote, until the file confirms it.
|
|
199
|
+
let filled = null;
|
|
198
200
|
// Provider readiness may outlast the session the selection was made for.
|
|
199
201
|
const adopted = lease.run(
|
|
200
202
|
() => {
|
|
203
|
+
// Compare-and-set: a route that already has a model (picked while this
|
|
204
|
+
// selection was resolving) is never replaced. Checked synchronously with
|
|
205
|
+
// the adopt below, so nothing can set a route in between. A Lead route
|
|
206
|
+
// another process persisted may not have reached this runtime yet (the
|
|
207
|
+
// change notification is debounced): the config is re-read from disk
|
|
208
|
+
// right here, which adopts it and fills the empty route, and that route
|
|
209
|
+
// is the winner this selection answers with.
|
|
210
|
+
if (options?.onlyIfEmpty === true) {
|
|
211
|
+
if (clean(getRoute()?.model)) return false;
|
|
212
|
+
deps.reloadFullConfig?.();
|
|
213
|
+
if (clean(getRoute()?.model)) return false;
|
|
214
|
+
}
|
|
201
215
|
if (options?.sessionOnly === true) {
|
|
202
216
|
// An agent child's route belongs to that session alone: the shared
|
|
203
217
|
// modelSettings bucket (saved effort/Fast per model) and the main
|
|
@@ -210,7 +224,9 @@ async function adoptRoute(deps, persist, next, options, lease) {
|
|
|
210
224
|
// — the session surface's model pick, an heir — keeps it.
|
|
211
225
|
const leadRoute = persist.persistAdoptedModelSettings(selectedRoute, {
|
|
212
226
|
keepMainModel: options?.keepMainModel !== false,
|
|
227
|
+
onlyIfAbsent: options?.onlyIfEmpty === true,
|
|
213
228
|
});
|
|
229
|
+
filled = leadRoute;
|
|
214
230
|
setRouteState(resolveRoute(getConfig(), leadRoute ? { model: workflowPresetId('lead') } : selectedRoute));
|
|
215
231
|
}
|
|
216
232
|
return true;
|
|
@@ -218,9 +234,13 @@ async function adoptRoute(deps, persist, next, options, lease) {
|
|
|
218
234
|
() => false
|
|
219
235
|
);
|
|
220
236
|
if (!adopted) return false;
|
|
237
|
+
// A conditional fill reaches the file only while no Lead route is stored
|
|
238
|
+
// there (judged under the config file lock). When another process wrote one
|
|
239
|
+
// after the re-read above, the file's route replaces the fill in the runtime.
|
|
240
|
+
const replaced = filled && (await deps.confirmLeadFill?.(filled)) === true;
|
|
221
241
|
// The refresh completes against the route just adopted and only while the
|
|
222
242
|
// selection still owns it.
|
|
223
|
-
await refreshRouteEffort(modelMeta, getRoute(), null, lease);
|
|
243
|
+
await refreshRouteEffort(replaced ? null : modelMeta, getRoute(), null, lease);
|
|
224
244
|
return lease.run(
|
|
225
245
|
() => {
|
|
226
246
|
refreshStatuslineUsageSnapshot(getRoute());
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from './native-web-search/tool-shape.mjs';
|
|
15
15
|
import { createWebSearchRouteCandidates } from './native-web-search/routes.mjs';
|
|
16
16
|
import { reportNativeWebSearchUsage } from './native-web-search-usage.mjs';
|
|
17
|
-
import {
|
|
17
|
+
import { canFallbackCapacityRoute } from '../runtime/shared/model-route-fallback.mjs';
|
|
18
18
|
|
|
19
19
|
export function createNativeWebSearch(deps) {
|
|
20
20
|
const {
|
|
@@ -103,7 +103,7 @@ export function createNativeWebSearch(deps) {
|
|
|
103
103
|
try {
|
|
104
104
|
return await sendCandidate(candidate, toolType, webSearchArgs, signal);
|
|
105
105
|
} catch (err) {
|
|
106
|
-
if (!
|
|
106
|
+
if (!canFallbackCapacityRoute(err, { signal })) throw err;
|
|
107
107
|
errors.push(
|
|
108
108
|
`${candidate.provider}${candidate.model ? `/${candidate.model}` : ''}/${toolType}: ${err?.message || String(err)}`
|
|
109
109
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
2
3
|
import { flushPendingSessionConfigWrites } from './config-lifecycle.mjs';
|
|
3
4
|
|
|
4
5
|
// Run only after the lifecycle has established that it needs a NEW session.
|
|
@@ -27,7 +28,11 @@ export function createNewSessionConfig({
|
|
|
27
28
|
if (!initialConfig || typeof initialConfig !== 'object') {
|
|
28
29
|
reloadFullConfig();
|
|
29
30
|
if (!firstSession || !initialRouteExplicit) {
|
|
30
|
-
|
|
31
|
+
const configured = resolveRoute(rt.config, {});
|
|
32
|
+
// With no Lead model configured the model picked for this runtime's
|
|
33
|
+
// still-empty conversation (a session-only choice never writes the
|
|
34
|
+
// Lead route) stands; the reload must not drop it before the first turn.
|
|
35
|
+
if (clean(configured?.model) || !clean(rt.route?.model)) rt.route = configured;
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
invalidateOutputStyleStatusCache();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Route resolution against the live runtime record:
|
|
2
|
-
//
|
|
1
|
+
// Route resolution against the live runtime record: refuse a model-less first
|
|
2
|
+
// turn (no auto-pick) and refresh the route's effort / Fast / context
|
|
3
3
|
// fields from provider metadata.
|
|
4
4
|
import { ensureProviderEnabled } from '../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
|
|
5
5
|
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
@@ -22,17 +22,11 @@ export function createRouteResolution({ rt, collectProviderModels, ensureProvide
|
|
|
22
22
|
if (routeHasModel()) return rt.route;
|
|
23
23
|
const models = await runAbortable(signal, () => collectProviderModels());
|
|
24
24
|
throwIfAborted(signal);
|
|
25
|
-
|
|
26
|
-
if (!picked) {
|
|
25
|
+
if (!models?.length) {
|
|
27
26
|
throw new Error('No provider models available. Open /providers to sign in, then /model to choose a model.');
|
|
28
27
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
provider: picked.provider,
|
|
32
|
-
model: picked.id,
|
|
33
|
-
preset: null,
|
|
34
|
-
};
|
|
35
|
-
return rt.route;
|
|
28
|
+
// Never auto-pick: the user chooses the model.
|
|
29
|
+
throw new Error('No model selected. Choose a model with /model first.');
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
async function refreshRouteEffort(modelMetaOverride = null, expectedRoute = null, signal = null, lease = null) {
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
import { PRESET_ALIASES } from './tool-def.mjs';
|
|
13
13
|
import { positiveInt } from '../../../runtime/shared/numbers.mjs';
|
|
14
14
|
import { clean } from '../../../runtime/shared/clean.mjs';
|
|
15
|
+
import { readAgentDefinitionKind } from '../../../runtime/shared/agent-kind.mjs';
|
|
16
|
+
import { isStoreBot, userDefinitionDir } from '../../../runtime/shared/agent-store.mjs';
|
|
15
17
|
import { normalizeContextTokens } from '../../../runtime/shared/llm/default-context-window.mjs';
|
|
16
18
|
|
|
17
19
|
export { positiveInt };
|
|
@@ -300,13 +302,18 @@ const _frontmatterPermCache = new Map(); // key -> { value, atMs }
|
|
|
300
302
|
const FRONTMATTER_PERM_CACHE_TTL_MS = envTimeoutMs('MIXDOG_AGENT_FRONTMATTER_TTL_MS', 5_000);
|
|
301
303
|
|
|
302
304
|
function agentDeleted(cleanAgent, dataDir) {
|
|
303
|
-
return Boolean(dataDir) && existsSync(join(dataDir,
|
|
305
|
+
return Boolean(dataDir) && existsSync(join(userDefinitionDir(dataDir, cleanAgent), '.deleted'));
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
/** Where an agent's definition may live, in precedence order: the data dir
|
|
307
309
|
* (user/custom agents) before the shipped source root. */
|
|
308
310
|
function agentDefinitionFiles(cleanAgent, dataDir, standaloneSourceRoot) {
|
|
309
311
|
const candidates = [];
|
|
312
|
+
if (dataDir && isStoreBot(dataDir, cleanAgent)) {
|
|
313
|
+
// A bot of the store: only its own folder (never the agents/ original).
|
|
314
|
+
candidates.push(join(userDefinitionDir(dataDir, cleanAgent), 'AGENT.md'));
|
|
315
|
+
return candidates;
|
|
316
|
+
}
|
|
310
317
|
if (dataDir) {
|
|
311
318
|
candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
|
|
312
319
|
candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
|
|
@@ -353,13 +360,25 @@ export function readAgentFrontmatterPermission(agent, dataDir, standaloneSourceR
|
|
|
353
360
|
}
|
|
354
361
|
|
|
355
362
|
const _frontmatterKindCache = new Map(); // key -> { value, atMs }
|
|
363
|
+
const _frontmatterKindValueCache = new Map(); // key -> { value, atMs }
|
|
356
364
|
|
|
357
|
-
/** The agent definition's
|
|
358
|
-
*
|
|
365
|
+
/** The agent definition's kind (isContactAgentId reads it), by the one
|
|
366
|
+
* read-only rule (agent-kind.mjs), cached per agent for the frontmatter TTL. */
|
|
359
367
|
export function readAgentFrontmatterKind(agent, dataDir, standaloneSourceRoot) {
|
|
360
|
-
|
|
361
|
-
|
|
368
|
+
const id = clean(agent);
|
|
369
|
+
if (!id || agentDeleted(id, dataDir)) return null;
|
|
370
|
+
const cacheKey = `${dataDir}\u0000${id}`;
|
|
371
|
+
const cached = _frontmatterKindCache.get(cacheKey);
|
|
372
|
+
if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) return cached.value;
|
|
373
|
+
// An explicit kind in the definition in effect (any layout) wins; only an
|
|
374
|
+
// unmarked user copy goes to the evidence rule.
|
|
375
|
+
const explicit = cachedFrontmatterValue(_frontmatterKindValueCache, id, dataDir, standaloneSourceRoot, (fm) =>
|
|
376
|
+
typeof fm.kind === 'string' && fm.kind.trim() ? fm.kind.trim().toLowerCase() : null
|
|
362
377
|
);
|
|
378
|
+
const value =
|
|
379
|
+
explicit || (dataDir ? readAgentDefinitionKind({ dataDir, shippedRoot: standaloneSourceRoot, id }) : null);
|
|
380
|
+
_frontmatterKindCache.set(cacheKey, { value, atMs: Date.now() });
|
|
381
|
+
return value;
|
|
363
382
|
}
|
|
364
383
|
|
|
365
384
|
/** The agent definition's `firstResponseTimeoutMs` (a non-negative integer;
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
terminalResultHook,
|
|
17
17
|
} from './job-task-reconcile.mjs';
|
|
18
18
|
import { createTurnReviewCollector } from './turn-review.mjs';
|
|
19
|
+
import { handoffContent } from '../../handoff-attachments.mjs';
|
|
19
20
|
import { settledWorkerStatus } from './worker-index/row-liveness.mjs';
|
|
20
21
|
|
|
21
22
|
// `send` to an existing worker: resolve the live session, run one follow-up
|
|
@@ -69,8 +70,10 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
69
70
|
return hooks;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
function runTurn({ session, sessionId, prompt, args }, turnHooks) {
|
|
73
|
+
function runTurn({ session, sessionId, prompt: text, args }, turnHooks) {
|
|
73
74
|
const cwd = session.cwd || defaultCwd;
|
|
75
|
+
// The caller's forwarded files ride along as attachment parts.
|
|
76
|
+
const prompt = handoffContent(text, args.attachmentParts);
|
|
74
77
|
if (canUseSessionSurface(session)) {
|
|
75
78
|
return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd, ...turnHooks });
|
|
76
79
|
}
|
|
@@ -194,7 +197,11 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
194
197
|
const { session, sessionId, prompt } = prepared;
|
|
195
198
|
const tag = registry.tagForSession(sessionId);
|
|
196
199
|
if (canUseSessionSurface(session) && views.isSessionBusy(sessionId)) {
|
|
197
|
-
const queued = await sessionSurface.enqueueTurn({
|
|
200
|
+
const queued = await sessionSurface.enqueueTurn({
|
|
201
|
+
session,
|
|
202
|
+
prompt: handoffContent(prompt, prepared.args.attachmentParts),
|
|
203
|
+
context: prepared.args.context || null,
|
|
204
|
+
});
|
|
198
205
|
if (queued) {
|
|
199
206
|
return renderResult({ queued: true, ...extras, tag, sessionId, agent: session.agent || null, ...queued });
|
|
200
207
|
}
|
|
@@ -204,7 +211,8 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
204
211
|
views.isSessionBusy(sessionId) &&
|
|
205
212
|
typeof mgr.enqueuePendingMessage === 'function'
|
|
206
213
|
) {
|
|
207
|
-
|
|
214
|
+
// The queued follow-up keeps the forwarded files, as the canonical queue does.
|
|
215
|
+
const queueDepth = mgr.enqueuePendingMessage(sessionId, handoffContent(prompt, prepared.args.attachmentParts));
|
|
208
216
|
return renderResult({ queued: true, ...extras, tag, sessionId, agent: session.agent || null, queueDepth });
|
|
209
217
|
}
|
|
210
218
|
const job = spawnFlow.startJob(
|
|
@@ -25,6 +25,7 @@ import { settledWorkerStatus } from '../worker-index/row-liveness.mjs';
|
|
|
25
25
|
import { saveSessionAsync } from '../../../../runtime/agent/orchestrator/session/store.mjs';
|
|
26
26
|
import { presetDescriptor } from './spawn-prep.mjs';
|
|
27
27
|
import { workerStageForStatus } from '../worker-rows.mjs';
|
|
28
|
+
import { handoffContent } from '../../../handoff-attachments.mjs';
|
|
28
29
|
|
|
29
30
|
// Mid-turn worker persistence cadence. The watchdog sweep runs every second;
|
|
30
31
|
// a transcript save is far more expensive than a bookkeeping stamp, so the
|
|
@@ -117,7 +118,9 @@ export function createSpawnRunner({
|
|
|
117
118
|
};
|
|
118
119
|
|
|
119
120
|
async function runTurn(prepared, turnHooks) {
|
|
120
|
-
const { args, session,
|
|
121
|
+
const { args, session, workerCwd } = prepared;
|
|
122
|
+
// The caller's forwarded files ride along as attachment parts.
|
|
123
|
+
const prompt = handoffContent(prepared.prompt, args.attachmentParts);
|
|
121
124
|
if (typeof sessionSurface?.runTurn === 'function' && sessionSurface.canRun?.(session) !== false) {
|
|
122
125
|
return sessionSurface.runTurn({ session, prompt, context: args.context || null, cwd: workerCwd, ...turnHooks });
|
|
123
126
|
}
|
|
@@ -27,11 +27,10 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const agentName = normalizeAgentName(args.agent);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
: null;
|
|
30
|
+
// The agent's stored route: config.agents, or a store bot's own folder.
|
|
31
|
+
const candidates = !clean(args.preset) ? configuredAgentRouteCandidates(config, agentName) : [];
|
|
32
|
+
const agentRoute = candidates.map((candidate) => normalizeAgentRoute(candidate)).find(Boolean) || null;
|
|
33
|
+
const storedFallbacks = candidates.find((candidate) => normalizeAgentRoute(candidate))?.fallbacks;
|
|
35
34
|
if (agentRoute) {
|
|
36
35
|
return {
|
|
37
36
|
presetName: agentPresetName(agentName),
|
|
@@ -41,9 +40,7 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
41
40
|
type: 'agent',
|
|
42
41
|
provider: agentRoute.provider,
|
|
43
42
|
model: agentRoute.model,
|
|
44
|
-
...(
|
|
45
|
-
? { fallbacks: config.agents[agentName].fallbacks.map(normalizeAgentRoute) }
|
|
46
|
-
: {}),
|
|
43
|
+
...(storedFallbacks?.length ? { fallbacks: storedFallbacks.map(normalizeAgentRoute) } : {}),
|
|
47
44
|
effort: agentRoute.effort,
|
|
48
45
|
fast: agentRoute.fast === true,
|
|
49
46
|
...(agentRoute.autoEffort === false ? { autoEffort: false } : {}),
|
|
@@ -53,6 +53,13 @@ export const AGENT_TOOL = {
|
|
|
53
53
|
file: { type: 'string', description: 'Path to task text; alternative to prompt/message for spawn/send.' },
|
|
54
54
|
cwd: { type: 'string', description: 'Project directory for a new or respawned agent.' },
|
|
55
55
|
context: { type: 'string', description: 'Extra context for the spawn/send turn.' },
|
|
56
|
+
attachments: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: { type: 'string' },
|
|
59
|
+
maxItems: 8,
|
|
60
|
+
description:
|
|
61
|
+
"spawn/send: files to attach (images, PDF/Office, text): a conversation attachment's file name or [attachment:id], or a path inside your folders. Produced files come back as paths; forward them by path.",
|
|
62
|
+
},
|
|
56
63
|
model: {
|
|
57
64
|
type: 'string',
|
|
58
65
|
description:
|
|
@@ -377,6 +377,17 @@ const SETUP_STATUS_READERS = {
|
|
|
377
377
|
// schema, the per-action field list and the route/object-argument minimums.
|
|
378
378
|
function validateSetupInput(args) {
|
|
379
379
|
const action = requireEnum(args?.action, SETUP_ACTIONS, 'action');
|
|
380
|
+
// Old callers still ask to connect GitHub: it is the signed-in GitHub CLI, not a catalog connector.
|
|
381
|
+
if (
|
|
382
|
+
action === 'connect_connector' &&
|
|
383
|
+
String(args.connector ?? '')
|
|
384
|
+
.trim()
|
|
385
|
+
.toLowerCase() === 'github'
|
|
386
|
+
) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
'GitHub uses the signed-in GitHub CLI; no connector needed. Use the github tool or Settings → Git.'
|
|
389
|
+
);
|
|
390
|
+
}
|
|
380
391
|
const validationError = schemaValueError({ ...args, action }, SETUP_TOOL_DEFS[0].inputSchema, 'setup');
|
|
381
392
|
if (validationError) throw new Error(`[tool-input-validation] ${validationError}`);
|
|
382
393
|
const fields = SETUP_ACTION_FIELDS[action].split(' ').filter(Boolean);
|
|
@@ -647,7 +658,7 @@ const SETUP_ACTION_HANDLERS = {
|
|
|
647
658
|
const entry = connectorById(requireText(args.connector, 'connector'));
|
|
648
659
|
if (!entry || entry.ownKey) throw new Error(`connector must be one of ${CONNECTOR_IDS.join(', ')}`);
|
|
649
660
|
const server = (rt.mcpStatus?.()?.servers || []).find((row) => row.connector === entry.id) || null;
|
|
650
|
-
const connected =
|
|
661
|
+
const connected = server?.connected === true;
|
|
651
662
|
return {
|
|
652
663
|
connector: entry.id,
|
|
653
664
|
name: entry.name,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { SETUP_EXTENDED_PROPERTIES, SETUP_DESKTOP_ACTIONS } from './settings-contract.mjs';
|
|
2
2
|
import { MEDIA_KINDS } from '../../runtime/media/tool-defs.mjs';
|
|
3
3
|
import { OPENAI_COMPAT_PRESETS } from '../../runtime/agent/orchestrator/providers/openai-compat-presets.mjs';
|
|
4
|
-
import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
4
|
+
import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
5
|
+
import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
|
|
6
|
+
import { BOT_AGENT_KIND, WORKFLOW_AGENT_KIND } from '../../runtime/shared/agent-route-config.mjs';
|
|
5
7
|
import { assertAutomationAccess, scheduleCallerScope } from '../services/schedule-access.mjs';
|
|
6
8
|
|
|
7
9
|
const own = (value, key) => Object.hasOwn(value || {}, key);
|
|
@@ -164,13 +166,20 @@ async function readDefinition(rt, kind, name) {
|
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
|
|
169
|
+
// A created agent's explicit marker: the one given, else a bot when Mixy's
|
|
170
|
+
// agent-mode chat creates it (Mixy creates bots), else a workflow agent.
|
|
171
|
+
function createdAgentKind(input, caller) {
|
|
172
|
+
if (own(input, 'kind')) return input.kind;
|
|
173
|
+
const { mode, agentId } = resolveSessionMode({ session: caller });
|
|
174
|
+
return mode === AGENT_MODE && agentId === MIXY_AGENT_ID ? BOT_AGENT_KIND : WORKFLOW_AGENT_KIND;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function saveDefinition(rt, args, context = {}) {
|
|
168
178
|
const kind = args.definitionKind;
|
|
169
179
|
const input = args.definition;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: ['id', 'name', 'description', 'body'];
|
|
180
|
+
let allowed = ['id', 'name', 'description', 'body'];
|
|
181
|
+
if (kind === 'skill') allowed = ['originalName', 'name', 'description', 'body', 'whenToUse', 'toolDependencies'];
|
|
182
|
+
else if (kind === 'agent') allowed = [...allowed, 'kind'];
|
|
174
183
|
const unexpected = Object.keys(input).find((key) => !allowed.includes(key));
|
|
175
184
|
if (unexpected) throw new Error(`${unexpected} is not a ${kind} definition field`);
|
|
176
185
|
const creating = args.action === 'create_definition';
|
|
@@ -200,7 +209,10 @@ async function saveDefinition(rt, args) {
|
|
|
200
209
|
const agents = await rt.listAgents();
|
|
201
210
|
if (agents.some((agent) => agent.id === input.id)) throw new Error(`agent "${input.id}" already exists`);
|
|
202
211
|
}
|
|
203
|
-
return rt.saveAgentDefinition(
|
|
212
|
+
if (creating) return rt.saveAgentDefinition({ ...next, kind: createdAgentKind(input, context.caller) });
|
|
213
|
+
// The stored kind is kept as it is; an edit naming one is refused by the editor.
|
|
214
|
+
const { kind: _storedKind, ...edit } = { ...pick(current, definitionKeys), ...input };
|
|
215
|
+
return rt.saveAgentDefinition({ ...edit, ...(own(input, 'kind') ? { kind: input.kind } : {}), id: current.id });
|
|
204
216
|
}
|
|
205
217
|
return creating ? rt.addSkill(next) : rt.saveSkill({ ...next, originalName: current.originalName });
|
|
206
218
|
}
|
|
@@ -157,6 +157,12 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
|
|
|
157
157
|
name: text,
|
|
158
158
|
description: text,
|
|
159
159
|
body: text,
|
|
160
|
+
kind: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
enum: ['bot', 'agent'],
|
|
163
|
+
description:
|
|
164
|
+
'Agent create only: "bot" (a contact the user chats with) or "agent" (a workflow sub-agent). Omitted: a bot when created from the representative bot\'s chat, else a workflow agent. Fixed after creation.',
|
|
165
|
+
},
|
|
160
166
|
whenToUse: text,
|
|
161
167
|
toolDependencies: {
|
|
162
168
|
type: ['array', 'null'],
|
|
@@ -343,7 +343,17 @@ export function createToolPolicyRefresh({
|
|
|
343
343
|
to: session.workflow.name,
|
|
344
344
|
at: Date.now(),
|
|
345
345
|
};
|
|
346
|
-
|
|
346
|
+
// Two switches can land in the same millisecond (A→B→A): the same
|
|
347
|
+
// switch is a repeat only when its direction matches too.
|
|
348
|
+
recordTranscriptBoundary(
|
|
349
|
+
session,
|
|
350
|
+
'workflowSwitches',
|
|
351
|
+
workflowSwitch,
|
|
352
|
+
(entry) =>
|
|
353
|
+
entry?.at === workflowSwitch.at &&
|
|
354
|
+
entry?.fromId === workflowSwitch.fromId &&
|
|
355
|
+
entry?.toId === workflowSwitch.toId
|
|
356
|
+
);
|
|
347
357
|
}
|
|
348
358
|
return { appliedToCurrentSession: true, workflow: session.workflow, workflowSwitch };
|
|
349
359
|
}
|
|
@@ -369,7 +379,8 @@ export function createToolPolicyRefresh({
|
|
|
369
379
|
const identity = session?.promptIdentity;
|
|
370
380
|
const agent = identity?.agent;
|
|
371
381
|
if (!session?.id || !agent || isAgentOwner(session)) return false;
|
|
372
|
-
|
|
382
|
+
// Only Mixy's own (agent-mode) thread embeds Mixy's profile.
|
|
383
|
+
const revision = agentProfileRevision(agent, { mixy: sessionModeOf(session) === AGENT_MODE });
|
|
373
384
|
const config = getConfig();
|
|
374
385
|
const workflowId = session.workflow?.id;
|
|
375
386
|
const workflowConfig = workflowId
|
|
@@ -10,6 +10,8 @@ import { throwIfAborted } from '../../../runtime/shared/abort-race.mjs';
|
|
|
10
10
|
// UserPromptSubmit hook.
|
|
11
11
|
export function createTurnPreparation({
|
|
12
12
|
getSession,
|
|
13
|
+
setSession,
|
|
14
|
+
mgr,
|
|
13
15
|
getCurrentCwd,
|
|
14
16
|
awaitRoutePreparation,
|
|
15
17
|
createCurrentSession,
|
|
@@ -105,6 +107,15 @@ export function createTurnPreparation({
|
|
|
105
107
|
}
|
|
106
108
|
throwIfAborted(turn.signal);
|
|
107
109
|
assertSessionAdmission?.();
|
|
110
|
+
// askSession runs the turn on the session the store serves for this id.
|
|
111
|
+
// While this runtime idled, the store's idle sweep may have released the
|
|
112
|
+
// snapshot it holds, and the store then serves a fresh parse of the file:
|
|
113
|
+
// preparing the turn on the held copy would leave its one-shot work (the
|
|
114
|
+
// first-turn MCP fold, its flag) undone on the turn's session, which then
|
|
115
|
+
// rewrites the prompt it already sent on the next turn. Prepare the turn
|
|
116
|
+
// on the session it runs on (as completeTurn adopts it afterwards).
|
|
117
|
+
const stored = mgr.getSession(getSession().id);
|
|
118
|
+
if (stored && stored !== getSession()) setSession(stored);
|
|
108
119
|
// Turn admission: waits while this session's workspace transitions or
|
|
109
120
|
// another session changes the checkout this session works in, then holds
|
|
110
121
|
// the turn registered (released by settleTurn) so no checkout changes
|