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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
clearAgentDefinitionCache,
|
|
13
13
|
} from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
14
14
|
import {
|
|
15
|
+
BOT_AGENT_KIND,
|
|
15
16
|
canonicalizeAgentRouteStorage,
|
|
16
17
|
isAgentDisabled,
|
|
17
18
|
WORKFLOW_AGENT_KIND,
|
|
@@ -43,9 +44,23 @@ function connectorPolicyInput(value) {
|
|
|
43
44
|
return storedConnectorPolicy(parseConnectorPolicy(value));
|
|
44
45
|
}
|
|
45
46
|
import { STANDALONE_ROOT } from '../runtime-paths.mjs';
|
|
47
|
+
import { homeThreadEvidence } from '../../runtime/shared/agent-kind.mjs';
|
|
48
|
+
import { BOT_SPLIT_BACKUP_DIRS, migrateBotsToOwnStore } from '../../runtime/shared/bot-split-migration.mjs';
|
|
49
|
+
import { backupUserData } from '../../runtime/shared/user-data-guard.mjs';
|
|
50
|
+
import { normalizeWorkflowRoute } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
51
|
+
import {
|
|
52
|
+
botDir,
|
|
53
|
+
botStoreActive,
|
|
54
|
+
migratedBotIds,
|
|
55
|
+
mixyIdentityDir,
|
|
56
|
+
readBotRoute,
|
|
57
|
+
readMixyRoute,
|
|
58
|
+
userDefinitionDir,
|
|
59
|
+
} from '../../runtime/shared/agent-store.mjs';
|
|
46
60
|
|
|
47
61
|
const isFixedAgent = (id) => FIXED_AGENT_SLOTS.some((agent) => agent.id === id);
|
|
48
|
-
const
|
|
62
|
+
const userDir = userDefinitionDir;
|
|
63
|
+
const hasUserAgentFile = (dataDir, id) => existsSync(join(userDir(dataDir, id), 'AGENT.md'));
|
|
49
64
|
// An archived (deleted) agent's AGENT.md, moved aside beside the tombstone:
|
|
50
65
|
// no loader reads it, and restoring moves it back.
|
|
51
66
|
const ARCHIVED_AGENT_FILE = 'AGENT.md.archived';
|
|
@@ -57,28 +72,36 @@ function hiddenFrontmatter(definition) {
|
|
|
57
72
|
return rest;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
// The definition's
|
|
61
|
-
//
|
|
75
|
+
// The definition's kind (isContactAgentId reads it), or null: the explicit
|
|
76
|
+
// marker bot creation (`kind: bot`) or the Workflows page (`kind: agent`)
|
|
77
|
+
// wrote, else the read-only decision for an unmarked one (agent-kind.mjs).
|
|
62
78
|
function kindOf(definition) {
|
|
63
|
-
|
|
64
|
-
return typeof kind === 'string' && kind.trim() ? kind.trim() : null;
|
|
79
|
+
return definition?.kind || null;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
// A save's `kind`: given only when creating,
|
|
68
|
-
// marker. Unmarked agents are
|
|
82
|
+
// A save's `kind`: given only when creating, as the bot or workflow-agent
|
|
83
|
+
// marker. Unmarked agents are workflow agents; a kind never changes later.
|
|
69
84
|
function creationKind(payload, existed) {
|
|
70
85
|
if (!hasOwn(payload, 'kind')) return null;
|
|
71
86
|
if (existed) throw new Error("an agent's kind is set when it is created and cannot change");
|
|
72
|
-
if (payload.kind !==
|
|
73
|
-
|
|
87
|
+
if (payload.kind !== BOT_AGENT_KIND && payload.kind !== WORKFLOW_AGENT_KIND) {
|
|
88
|
+
throw new Error(`agent kind must be "${BOT_AGENT_KIND}", "${WORKFLOW_AGENT_KIND}" or absent`);
|
|
89
|
+
}
|
|
90
|
+
return payload.kind;
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
function readManifest(dir) {
|
|
94
|
+
return readManifestStrict(join(dir, 'agent.json')) || {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The manifest object at `file`, or null when it is missing, unreadable or
|
|
98
|
+
// not a JSON object.
|
|
99
|
+
function readManifestStrict(file) {
|
|
77
100
|
try {
|
|
78
|
-
const value = JSON.parse(readFileSync(
|
|
79
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value :
|
|
101
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
102
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
80
103
|
} catch {
|
|
81
|
-
return
|
|
104
|
+
return null;
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
|
|
@@ -238,13 +261,18 @@ function writeAgentFiles(dir, { name, description, body, extra = {}, manifest =
|
|
|
238
261
|
// user-authored agents are custom definitions under agents/<id>/AGENT.md.
|
|
239
262
|
export function createAgentEditorApi(deps) {
|
|
240
263
|
const { getConfig, loadAgentDefinition, listCustomAgentIds, saveConfigAndAdopt, setAgentRoute } = deps;
|
|
241
|
-
|
|
264
|
+
// A store bot's route lives in its folder; every other agent's in the config.
|
|
265
|
+
const ownRoute = (config, id) => {
|
|
266
|
+
const bot = readBotRoute(resolveDataDir(deps), id);
|
|
267
|
+
return bot !== undefined ? bot : deps.agentRouteFromConfig(config, id);
|
|
268
|
+
};
|
|
269
|
+
const routeFor = (config, id) => effectiveAgentRoute({ ...deps, agentRouteFromConfig: ownRoute }, config, id);
|
|
242
270
|
// `route` falls back to Main; this says whether the agent has a route of
|
|
243
271
|
// its own, so a surface never shows (or saves) the Main fallback as one.
|
|
244
|
-
const routeConfigured = (config, id) => Boolean(
|
|
272
|
+
const routeConfigured = (config, id) => Boolean(ownRoute(config, id));
|
|
245
273
|
const shippedDir = (id) => join(deps.rootDir ?? STANDALONE_ROOT, 'agents', id);
|
|
246
274
|
// The manifest in effect: the user's own copy if any, else the shipped one.
|
|
247
|
-
const manifestDirOf = (dataDir, id) => (hasUserAgentFile(dataDir, id) ?
|
|
275
|
+
const manifestDirOf = (dataDir, id) => (hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id));
|
|
248
276
|
const manifestOf = (dataDir, id) => readManifest(manifestDirOf(dataDir, id));
|
|
249
277
|
const avatarOf = (dataDir, id) => normalizeAgentAvatar(manifestOf(dataDir, id).avatar);
|
|
250
278
|
// A saved, archived or restored bot reaches the prompts that embed it: the
|
|
@@ -292,7 +320,7 @@ export function createAgentEditorApi(deps) {
|
|
|
292
320
|
// writes, from the copy in effect. An editor that loaded it can send it back
|
|
293
321
|
// as `expectedRevision`, and a save over a newer copy is refused.
|
|
294
322
|
function definitionRevision(dataDir, id) {
|
|
295
|
-
const dir = hasUserAgentFile(dataDir, id) ?
|
|
323
|
+
const dir = hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id);
|
|
296
324
|
const hash = createHash('sha256');
|
|
297
325
|
for (const name of ['agent.json', 'AGENT.md', PERSONA_FILE]) {
|
|
298
326
|
hash.update(`${name}\0`);
|
|
@@ -326,7 +354,7 @@ export function createAgentEditorApi(deps) {
|
|
|
326
354
|
kind: kindOf(definition),
|
|
327
355
|
disabled: isAgentDisabled(getConfig(), id),
|
|
328
356
|
avatar: avatarOf(dataDir, id),
|
|
329
|
-
persona: readPersona(hasUserAgentFile(dataDir, id) ?
|
|
357
|
+
persona: readPersona(hasUserAgentFile(dataDir, id) ? userDir(dataDir, id) : shippedDir(id)),
|
|
330
358
|
revision: definitionRevision(dataDir, id),
|
|
331
359
|
};
|
|
332
360
|
}
|
|
@@ -345,7 +373,14 @@ export function createAgentEditorApi(deps) {
|
|
|
345
373
|
id ||
|
|
346
374
|
availableAgentId(
|
|
347
375
|
name,
|
|
348
|
-
(candidate) =>
|
|
376
|
+
(candidate) =>
|
|
377
|
+
Boolean(loadAgentDefinition(dataDir, candidate)) ||
|
|
378
|
+
isArchivedAgent(dataDir, candidate) ||
|
|
379
|
+
// An id the split copied stays the bot's, even once purged; a
|
|
380
|
+
// folder in either store keeps its id too.
|
|
381
|
+
migratedBotIds(dataDir).has(candidate) ||
|
|
382
|
+
(botStoreActive(dataDir) &&
|
|
383
|
+
(existsSync(join(dataDir, 'agents', candidate)) || existsSync(botDir(dataDir, candidate))))
|
|
349
384
|
),
|
|
350
385
|
name,
|
|
351
386
|
description: oneLine(payload.description),
|
|
@@ -371,7 +406,21 @@ export function createAgentEditorApi(deps) {
|
|
|
371
406
|
const kind = creationKind(payload, existed);
|
|
372
407
|
const definition = { name: name || id, description, body };
|
|
373
408
|
const manifest = hiddenManifest(manifestOf(dataDir, id));
|
|
374
|
-
|
|
409
|
+
// With the bot store, a new bot is made in bots/ and a new agent in
|
|
410
|
+
// agents/; either keeps its `kind` marker, so its kind never rests on
|
|
411
|
+
// the folder alone (a copy read without the store is still a bot).
|
|
412
|
+
const storeBot = !existed && kind === BOT_AGENT_KIND && botStoreActive(dataDir);
|
|
413
|
+
// An id is one definition's in one store: a new bot never takes an id
|
|
414
|
+
// agents/ holds (an archived agent's included), a new agent never one of
|
|
415
|
+
// bots/ (or one the split moved there, even if since deleted).
|
|
416
|
+
if (!existed && botStoreActive(dataDir)) {
|
|
417
|
+
const taken = storeBot
|
|
418
|
+
? existsSync(join(dataDir, 'agents', id))
|
|
419
|
+
: existsSync(botDir(dataDir, id)) || migratedBotIds(dataDir).has(id);
|
|
420
|
+
if (taken) throw new Error(`agent id "${id}" is already in use; pick another name`);
|
|
421
|
+
}
|
|
422
|
+
const dir = storeBot ? botDir(dataDir, id) : userDir(dataDir, id);
|
|
423
|
+
const inStore = dir === botDir(dataDir, id);
|
|
375
424
|
// New custom images arrive as checked bytes, stored under content hashes.
|
|
376
425
|
const update = hasOwn(payload, 'avatar') ? avatarUpdate(payload.avatar, dir) : null;
|
|
377
426
|
if (update?.avatar) manifest.avatar = update.avatar;
|
|
@@ -389,6 +438,7 @@ export function createAgentEditorApi(deps) {
|
|
|
389
438
|
// Equal to the shipped agent: keep no copy so it follows the shipped
|
|
390
439
|
// version — unless the manifest (an avatar) or a persona differs.
|
|
391
440
|
const absorbed =
|
|
441
|
+
!inStore &&
|
|
392
442
|
!persona &&
|
|
393
443
|
stableJson(manifest) === stableJson(hiddenManifest(readManifest(shippedDir(id)))) &&
|
|
394
444
|
absorbSaveEqualToShipped({
|
|
@@ -440,7 +490,7 @@ export function createAgentEditorApi(deps) {
|
|
|
440
490
|
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and cannot be deleted`);
|
|
441
491
|
const builtIn = isFixedAgent(id);
|
|
442
492
|
const dataDir = resolveDataDir(deps);
|
|
443
|
-
const dir =
|
|
493
|
+
const dir = userDir(dataDir, id);
|
|
444
494
|
if (!hasUserAgentFile(dataDir, id)) {
|
|
445
495
|
if (builtIn) throw new Error(`agent "${id}" has no user override to reset`);
|
|
446
496
|
if (!loadAgentDefinition(dataDir, id)) throw new Error(`agent "${id}" not found`);
|
|
@@ -470,7 +520,7 @@ export function createAgentEditorApi(deps) {
|
|
|
470
520
|
return { id, deleted: true, revertedToBuiltIn: false };
|
|
471
521
|
}
|
|
472
522
|
|
|
473
|
-
const isArchivedAgent = (dataDir, id) => existsSync(join(dataDir,
|
|
523
|
+
const isArchivedAgent = (dataDir, id) => existsSync(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
474
524
|
|
|
475
525
|
/** An archived agent back as it was, under its own id: its AGENT.md moved
|
|
476
526
|
* back (a starter without a copy of its own returns to the packaged one)
|
|
@@ -480,7 +530,7 @@ export function createAgentEditorApi(deps) {
|
|
|
480
530
|
if (!id || isHiddenAgent(id) || isFixedAgent(id)) throw new Error(`unknown agent "${agentId}"`);
|
|
481
531
|
const dataDir = resolveDataDir(deps);
|
|
482
532
|
if (!isArchivedAgent(dataDir, id)) throw new Error(`agent "${id}" is not deleted`);
|
|
483
|
-
const dir =
|
|
533
|
+
const dir = userDir(dataDir, id);
|
|
484
534
|
const kept = join(dir, ARCHIVED_AGENT_FILE);
|
|
485
535
|
if (!existsSync(kept) && !existsSync(join(shippedDir(id), 'AGENT.md'))) {
|
|
486
536
|
throw new Error(`agent "${id}" was deleted before deleted agents were kept, so it cannot be restored`);
|
|
@@ -501,7 +551,7 @@ export function createAgentEditorApi(deps) {
|
|
|
501
551
|
const dataDir = resolveDataDir(deps);
|
|
502
552
|
// Never a live agent; one deleted before archiving existed left nothing.
|
|
503
553
|
if (!isArchivedAgent(dataDir, id)) return { id, purged: false };
|
|
504
|
-
const dir =
|
|
554
|
+
const dir = userDir(dataDir, id);
|
|
505
555
|
rmSync(dir, { recursive: true, force: true });
|
|
506
556
|
clearAgentDefinitionCache(id);
|
|
507
557
|
dropAgentRoute(id);
|
|
@@ -522,7 +572,7 @@ export function createAgentEditorApi(deps) {
|
|
|
522
572
|
ids.map((agentId) => {
|
|
523
573
|
const id = agentEditorId(agentId);
|
|
524
574
|
if (!id || id !== agentId || !isArchivedAgent(dataDir, id)) return [agentId, null];
|
|
525
|
-
const manifest = readManifest(
|
|
575
|
+
const manifest = readManifest(userDir(dataDir, id));
|
|
526
576
|
return [agentId, { name: oneLine(manifest.name) || id, avatar: normalizeAgentAvatar(manifest.avatar) ?? null }];
|
|
527
577
|
})
|
|
528
578
|
);
|
|
@@ -547,9 +597,9 @@ export function createAgentEditorApi(deps) {
|
|
|
547
597
|
const presence = (agentId) => {
|
|
548
598
|
const id = agentEditorId(agentId);
|
|
549
599
|
if (!id || id !== agentId) return 'unknown';
|
|
550
|
-
const user = probe(join(dataDir,
|
|
600
|
+
const user = probe(join(userDir(dataDir, id), 'AGENT.md'));
|
|
551
601
|
if (user !== 'absent') return user;
|
|
552
|
-
const tombstone = probe(join(dataDir,
|
|
602
|
+
const tombstone = probe(join(userDir(dataDir, id), AGENT_DELETED_MARKER));
|
|
553
603
|
if (tombstone === 'present') return 'deleted';
|
|
554
604
|
if (tombstone === 'unknown') return 'unknown';
|
|
555
605
|
const shipped = probe(join(rootDir, 'agents', id, 'AGENT.md'));
|
|
@@ -566,9 +616,10 @@ export function createAgentEditorApi(deps) {
|
|
|
566
616
|
const id = agentEditorId(agentId);
|
|
567
617
|
if (!id || isHiddenAgent(id)) return null;
|
|
568
618
|
const dataDir = resolveDataDir(deps);
|
|
569
|
-
// Mixy's face lives in
|
|
619
|
+
// Mixy's face lives in its identity folder's agent.json (mixyIdentityDir:
|
|
620
|
+
// bots/mixy with the bot store, agents/lead before), beside no AGENT.md.
|
|
570
621
|
const lead = id === LEAD_AGENT_ID;
|
|
571
|
-
const dir =
|
|
622
|
+
const dir = lead ? leadDir() : userDir(dataDir, id);
|
|
572
623
|
// An archived agent's face stays readable for its archived chat.
|
|
573
624
|
const archived = !lead && isArchivedAgent(dataDir, id);
|
|
574
625
|
const avatar = lead
|
|
@@ -587,8 +638,10 @@ export function createAgentEditorApi(deps) {
|
|
|
587
638
|
|
|
588
639
|
// Mixy (the Lead) has no agent definition to edit: only its PERSONA.md
|
|
589
640
|
// (성격) and AGENT.md (일하는 방식, agent mode only), read by the runtime
|
|
590
|
-
// from
|
|
591
|
-
|
|
641
|
+
// from Mixy's identity folder: bots/mixy once the bot store is active,
|
|
642
|
+
// agents/lead before (mixyIdentityDir, agent-store.mjs). Saved empty, the
|
|
643
|
+
// file goes.
|
|
644
|
+
const leadDir = () => mixyIdentityDir(resolveDataDir(deps));
|
|
592
645
|
function readLeadFile(file) {
|
|
593
646
|
try {
|
|
594
647
|
return readFileSync(join(leadDir(), file), 'utf8').trim();
|
|
@@ -614,16 +667,27 @@ export function createAgentEditorApi(deps) {
|
|
|
614
667
|
const saveLeadAgentBody = (text) => saveLeadFile('AGENT.md', text);
|
|
615
668
|
|
|
616
669
|
// Whether Mixy was created (the "대표 에이전트 만들기" first run): the
|
|
617
|
-
// explicit `created` flag in
|
|
670
|
+
// explicit `created` flag in Mixy's agent.json (leadDir). null until decided —
|
|
618
671
|
// the desktop host decides once (an existing profile counts as created).
|
|
619
672
|
// Also Mixy's display name (`name`, read by the runtime's identity line) and
|
|
620
673
|
// face (`avatar`, as every agent manifest): '' / null mean the defaults.
|
|
674
|
+
// `homeChatted`: whether Mixy's home thread holds any conversation (the
|
|
675
|
+
// host reads it to tell v1.3.0's never-used automatic Mixy apart).
|
|
621
676
|
function getLeadStatus() {
|
|
622
677
|
const manifest = readManifest(leadDir());
|
|
678
|
+
// A v1.3.0 automatic Mixy the split left in agents/lead still counts as
|
|
679
|
+
// decided (its routines keep running) until bots/mixy says otherwise.
|
|
680
|
+
const legacy = readManifest(join(resolveDataDir(deps), 'agents', LEAD_AGENT_ID)).created;
|
|
681
|
+
let created = null;
|
|
682
|
+
if (typeof manifest.created === 'boolean') created = manifest.created;
|
|
683
|
+
else if (typeof legacy === 'boolean') created = legacy;
|
|
623
684
|
return {
|
|
624
|
-
created
|
|
685
|
+
created,
|
|
625
686
|
name: oneLine(manifest.name),
|
|
626
687
|
avatar: normalizeAgentAvatar(manifest.avatar) ?? null,
|
|
688
|
+
homeChatted: homeThreadEvidence(resolveDataDir(deps), LEAD_AGENT_ID).chatted,
|
|
689
|
+
// Mixy's own model (never the Lead's); null: its thread keeps its own.
|
|
690
|
+
route: readMixyRoute(resolveDataDir(deps)),
|
|
627
691
|
};
|
|
628
692
|
}
|
|
629
693
|
// Mixy's name and face, chosen in the 대표 에이전트 dialog or its profile:
|
|
@@ -648,8 +712,15 @@ export function createAgentEditorApi(deps) {
|
|
|
648
712
|
return getLeadStatus();
|
|
649
713
|
}
|
|
650
714
|
// Sets the flag (and Mixy's 하는 일 when given); every other key is kept.
|
|
715
|
+
// A Lead manifest that exists but cannot be read as an object is the
|
|
716
|
+
// user's and is left alone: nothing is written, the status stays as read.
|
|
651
717
|
function setLeadCreated(created, description) {
|
|
652
718
|
if (typeof created !== 'boolean') throw new TypeError('created must be a boolean');
|
|
719
|
+
const file = join(leadDir(), 'agent.json');
|
|
720
|
+
if (existsSync(file) && !readManifestStrict(file)) {
|
|
721
|
+
process.stderr.write(`[agents] ${file} is not a readable JSON object; Mixy's created flag was not saved\n`);
|
|
722
|
+
return getLeadStatus();
|
|
723
|
+
}
|
|
653
724
|
const manifest = readManifest(leadDir());
|
|
654
725
|
manifest.created = created;
|
|
655
726
|
const job = oneLine(description);
|
|
@@ -691,7 +762,27 @@ export function createAgentEditorApi(deps) {
|
|
|
691
762
|
return getAgentConnectorPolicy(id);
|
|
692
763
|
}
|
|
693
764
|
|
|
765
|
+
/** The one-time split of bots into bots/ (bot-split-migration.mjs), from
|
|
766
|
+
* the config as it is now; the catalogs read the store from here on. */
|
|
767
|
+
function migrateBotStore() {
|
|
768
|
+
const dataDir = resolveDataDir(deps);
|
|
769
|
+
const config = getConfig();
|
|
770
|
+
const result = migrateBotsToOwnStore({
|
|
771
|
+
dataDir,
|
|
772
|
+
shippedRoot: deps.rootDir ?? STANDALONE_ROOT,
|
|
773
|
+
routeOf: (id) => deps.agentRouteFromConfig(config, id),
|
|
774
|
+
leadRoute: normalizeWorkflowRoute(deps.resolveRoute(config, {})),
|
|
775
|
+
// The definitions the split copies or a re-sync replaces go with it.
|
|
776
|
+
backup: () => backupUserData(dataDir, 'bot-split', { dirs: BOT_SPLIT_BACKUP_DIRS }),
|
|
777
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
778
|
+
});
|
|
779
|
+
clearAgentDefinitionCache();
|
|
780
|
+
listCustomAgentIds?.(dataDir);
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
|
|
694
784
|
return {
|
|
785
|
+
migrateBotStore,
|
|
695
786
|
getAgentConnectorPolicy,
|
|
696
787
|
setAgentConnectorPolicy,
|
|
697
788
|
listAgents,
|
|
@@ -4,8 +4,22 @@ import { isHiddenAgent } from '../../runtime/agent/orchestrator/internal-agents.
|
|
|
4
4
|
import { normalizeWorkflowRoute } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
|
|
5
5
|
import { ensureProviderEnabled } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
|
|
6
6
|
import { resolveRouteContextState, selectionFastCapable } from '../route-state.mjs';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
canonicalizeAgentRouteStorage,
|
|
9
|
+
isAgentDisabled,
|
|
10
|
+
withAgentDisabled,
|
|
11
|
+
} from '../../runtime/shared/agent-route-config.mjs';
|
|
12
|
+
import { agentEditorId, resolveDataDir } from './shared.mjs';
|
|
13
|
+
import {
|
|
14
|
+
isStoreBot,
|
|
15
|
+
readBotRoute,
|
|
16
|
+
readMixyRoute,
|
|
17
|
+
writeBotRoute,
|
|
18
|
+
writeMixyRoute,
|
|
19
|
+
} from '../../runtime/shared/agent-store.mjs';
|
|
20
|
+
|
|
21
|
+
/** Mixy's runtime id (agent-home.mjs MIXY_AGENT_ID). */
|
|
22
|
+
const MIXY_RUNTIME_ID = 'lead';
|
|
9
23
|
import { routeChain, validateRouteChainProviders } from '../../runtime/shared/model-route-chain.mjs';
|
|
10
24
|
|
|
11
25
|
// Agent route persistence. Route-scope isolation: an agent route owns its
|
|
@@ -23,8 +37,35 @@ export function createAgentRouteApi(deps) {
|
|
|
23
37
|
saveConfigAndAdopt(canonicalizeAgentRouteStorage(withAgentDisabled(getConfig(), id, disabled)));
|
|
24
38
|
}
|
|
25
39
|
|
|
40
|
+
// A bot of the store keeps its route in its own folder (agent-store.mjs);
|
|
41
|
+
// Mixy (`lead` here, its runtime id) keeps its own in bots/mixy — never the
|
|
42
|
+
// Lead's route, which the Main route setter owns.
|
|
43
|
+
const dataDir = () => (deps.cfgMod || deps.STANDALONE_DATA_DIR ? resolveDataDir(deps) : '');
|
|
44
|
+
const isMixy = (id) => id === MIXY_RUNTIME_ID && Boolean(dataDir());
|
|
45
|
+
const storeBot = (id) => isStoreBot(dataDir(), id);
|
|
46
|
+
function saveRoute(id, route, keepOff) {
|
|
47
|
+
if (isMixy(id)) {
|
|
48
|
+
writeMixyRoute(dataDir(), route);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (storeBot(id)) {
|
|
52
|
+
writeBotRoute(dataDir(), id, route);
|
|
53
|
+
saveDisabled(id, keepOff);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const nextConfig = withAgentDisabled(getConfig(), id, keepOff);
|
|
57
|
+
nextConfig.agents = { ...(nextConfig.agents || {}), [id]: route };
|
|
58
|
+
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
// Only an explicitly empty provider removes an override: the agent inherits Main.
|
|
27
62
|
function clearOverride(id) {
|
|
63
|
+
if (isMixy(id) || storeBot(id)) {
|
|
64
|
+
if (isMixy(id)) writeMixyRoute(dataDir(), null);
|
|
65
|
+
else writeBotRoute(dataDir(), id, null);
|
|
66
|
+
const inherited = normalizeWorkflowRoute(resolveRoute(getConfig(), {}));
|
|
67
|
+
return { ...(inherited || {}), inherited: true };
|
|
68
|
+
}
|
|
28
69
|
const nextConfig = { ...getConfig() };
|
|
29
70
|
const agents = { ...(nextConfig.agents || {}) };
|
|
30
71
|
const hadOverride = Object.hasOwn(agents, id);
|
|
@@ -83,7 +124,20 @@ export function createAgentRouteApi(deps) {
|
|
|
83
124
|
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
84
125
|
if (isHiddenAgent(id)) throw new Error(`agent "${id}" is internal and has no configurable route`);
|
|
85
126
|
const requested = { ...(next || {}) };
|
|
86
|
-
|
|
127
|
+
// Compare-and-set: fill the route only while the agent has none. A route
|
|
128
|
+
// picked while the model resolved below stays (answered with `skipped`).
|
|
129
|
+
const onlyIfEmpty = requested.onlyIfEmpty === true;
|
|
130
|
+
delete requested.onlyIfEmpty;
|
|
131
|
+
const readStored = () => {
|
|
132
|
+
if (isMixy(id)) return readMixyRoute(dataDir()) || {};
|
|
133
|
+
if (storeBot(id)) return readBotRoute(dataDir(), id) || {};
|
|
134
|
+
return agentRouteFromConfig(getConfig(), id) || {};
|
|
135
|
+
};
|
|
136
|
+
const kept = () => ({ ...readStored(), id, skipped: true });
|
|
137
|
+
const stored = readStored();
|
|
138
|
+
if (onlyIfEmpty && clean(stored.provider)) return kept();
|
|
139
|
+
// Mixy is never off: its route request is a model pick alone.
|
|
140
|
+
if (isMixy(id)) delete requested.disabled;
|
|
87
141
|
// Off is an explicit state, stored apart from the route so the model the
|
|
88
142
|
// user picked survives and comes back when the agent is switched on. An
|
|
89
143
|
// off request that also carries a model saves that model and stays off.
|
|
@@ -126,11 +180,17 @@ export function createAgentRouteApi(deps) {
|
|
|
126
180
|
const [primary, ...fallbacks] = resolved;
|
|
127
181
|
const routeToSave = normalizeWorkflowRoute({ ...primary, fallbacks });
|
|
128
182
|
if (!routeToSave) throw new Error('agent route requires provider and model');
|
|
183
|
+
// No await from here to the write: the emptiness checked is the emptiness
|
|
184
|
+
// written over. An off agent stays off when it was only back-filled.
|
|
185
|
+
// A store bot's or Mixy's route is read from its folder on disk right here.
|
|
186
|
+
// An agent routed in the config file may have been written by another
|
|
187
|
+
// process whose change notification is still pending: re-read it first.
|
|
188
|
+
if (onlyIfEmpty && !isMixy(id) && !storeBot(id)) deps.reloadFullConfig?.();
|
|
189
|
+
if (onlyIfEmpty && clean(readStored().provider)) return kept();
|
|
190
|
+
const stayOff = onlyIfEmpty ? isAgentDisabled(getConfig(), id) : keepOff;
|
|
129
191
|
// Picking a model is also the "on" switch unless the request keeps it off.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
133
|
-
return keepOff ? { ...routeToSave, id, disabled: true } : routeToSave;
|
|
192
|
+
saveRoute(id, routeToSave, stayOff);
|
|
193
|
+
return stayOff ? { ...routeToSave, id, disabled: true } : routeToSave;
|
|
134
194
|
}
|
|
135
195
|
|
|
136
196
|
return { setAgentRoute };
|
|
@@ -25,6 +25,7 @@ function applyWorkflowSlots(workflowInput, draft) {
|
|
|
25
25
|
if (!normalized) continue;
|
|
26
26
|
draft.presets = upsertWorkflowPreset(draft.presets, 'lead', normalized);
|
|
27
27
|
draft.defaultPreset = workflowPresetId('lead');
|
|
28
|
+
draft.leadRoute = normalized;
|
|
28
29
|
} else if (slot === 'agent') {
|
|
29
30
|
if (normalized) draft.agentRoutes.worker = normalized;
|
|
30
31
|
else delete draft.agentRoutes.worker;
|
|
@@ -72,10 +73,13 @@ export function createOnboardingApi(deps) {
|
|
|
72
73
|
presets: Array.isArray(nextConfig.presets) ? nextConfig.presets.slice() : [],
|
|
73
74
|
defaultPreset: nextConfig.default,
|
|
74
75
|
agentRoutes: { ...(nextConfig.agents || {}) },
|
|
76
|
+
// The Lead route this save writes, from `defaultRoute` or `workflowRoutes.lead`.
|
|
77
|
+
leadRoute: null,
|
|
75
78
|
};
|
|
76
79
|
if (defaultRoute) {
|
|
77
80
|
draft.presets = upsertWorkflowPreset(draft.presets, 'lead', defaultRoute);
|
|
78
81
|
draft.defaultPreset = workflowPresetId('lead');
|
|
82
|
+
draft.leadRoute = defaultRoute;
|
|
79
83
|
}
|
|
80
84
|
const workflowInput =
|
|
81
85
|
payload.workflowRoutes && typeof payload.workflowRoutes === 'object' ? payload.workflowRoutes : {};
|
|
@@ -104,12 +108,15 @@ export function createOnboardingApi(deps) {
|
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
107
|
-
|
|
111
|
+
// Whatever wrote the Lead route also moves the runtime Main route, in the
|
|
112
|
+
// same synchronous step as the save: a pending conditional backfill then
|
|
113
|
+
// finds a route that is not empty instead of overwriting this choice.
|
|
114
|
+
if (draft.leadRoute) {
|
|
108
115
|
setRouteState(
|
|
109
116
|
resolveRoute(getConfig(), {
|
|
110
|
-
provider:
|
|
111
|
-
model:
|
|
112
|
-
effort:
|
|
117
|
+
provider: draft.leadRoute.provider,
|
|
118
|
+
model: draft.leadRoute.model,
|
|
119
|
+
effort: draft.leadRoute.effort,
|
|
113
120
|
})
|
|
114
121
|
);
|
|
115
122
|
deps.invalidatePreSessionToolSurface?.();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SESSION_ID_PATTERN } from '../runtime/agent/orchestrator/runtime-core/session-id.mjs';
|
|
2
2
|
import { sessionListOutput, sessionReadOutput, SESSION_TOOL_OUTPUT_BYTES } from './session-tool-output.mjs';
|
|
3
3
|
import { createAgentTaskSessions } from '../session-runtime/agent-task-sessions.mjs';
|
|
4
|
+
import { handoffContent } from '../session-runtime/handoff-attachments.mjs';
|
|
4
5
|
|
|
5
6
|
const TARGET_ACTIONS = new Set(['status', 'read', 'send', 'cancel', 'delete']);
|
|
6
7
|
const MUTATING_ACTIONS = new Set(['send', 'cancel', 'delete']);
|
|
@@ -51,15 +52,22 @@ export function createSessionControl({
|
|
|
51
52
|
if (typeof args.prompt !== 'string' || !args.prompt.trim()) {
|
|
52
53
|
throw new Error('session: prompt is required for action=send');
|
|
53
54
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// An agent conversation's send reports its turn back to the agent's
|
|
56
|
+
// home: a task session it created by its stored origin, any other
|
|
57
|
+
// session by an origin for this send only.
|
|
58
|
+
// Forwarded files ride along as stored attachment parts (workspace-tools.mjs).
|
|
59
|
+
const prompt = handoffContent(args.prompt, context.attachmentParts);
|
|
60
|
+
const origin = await agentTasks.reportOrigin(sessionId, context.callerAgentId, caller);
|
|
61
|
+
if (origin) {
|
|
62
|
+
const { accepted, reportsBack } = await agentTasks.send(sessionId, prompt, origin);
|
|
63
|
+
return { sessionId, accepted, reportsBack };
|
|
64
|
+
}
|
|
57
65
|
const result = await service.submitSession({
|
|
58
66
|
sessionId,
|
|
59
|
-
prompt
|
|
67
|
+
prompt,
|
|
60
68
|
options: { mode: 'prompt', transcriptMeta: { sender: 'lead' } },
|
|
61
69
|
});
|
|
62
|
-
return { sessionId, accepted: result.accepted === true };
|
|
70
|
+
return { sessionId, accepted: result.accepted === true, reportsBack: false };
|
|
63
71
|
}
|
|
64
72
|
if (action === 'cancel') {
|
|
65
73
|
const result = await service.abortSession({ sessionId });
|
|
@@ -96,6 +96,7 @@ import { createChannelsRuntimeLoader } from './daemon-channels-loader.mjs';
|
|
|
96
96
|
import { createDesktopRuntime } from './daemon-desktop-runtime.mjs';
|
|
97
97
|
import { createCanonicalAgentControl } from './daemon-agent-control.mjs';
|
|
98
98
|
import { createSessionControl } from './daemon-session-control.mjs';
|
|
99
|
+
import { createAgentTaskSessions } from '../session-runtime/agent-task-sessions.mjs';
|
|
99
100
|
import { flushAllIndexWrites } from '../session-runtime/services/agent-tool/index-write-queue.mjs';
|
|
100
101
|
import {
|
|
101
102
|
compareRuntimeVersions,
|
|
@@ -926,6 +927,8 @@ async function main() {
|
|
|
926
927
|
cwd: CWD,
|
|
927
928
|
});
|
|
928
929
|
canonicalAgentControl = agentControl;
|
|
930
|
+
// Agents' cross-session sends and task sessions, and their report watches.
|
|
931
|
+
const agentTasks = createAgentTaskSessions({ getSessionService: () => sessionService });
|
|
929
932
|
const requestedRuntimeMode = String(process.env.MIXDOG_SESSION_RUNTIME_MODE || '')
|
|
930
933
|
.trim()
|
|
931
934
|
.toLowerCase();
|
|
@@ -939,7 +942,7 @@ async function main() {
|
|
|
939
942
|
log,
|
|
940
943
|
measureBootPhase: bootPhases.measure,
|
|
941
944
|
executeAgentControl: agentControl.execute,
|
|
942
|
-
executeSessionControl: createSessionControl({ getSessionService: () => sessionService }),
|
|
945
|
+
executeSessionControl: createSessionControl({ getSessionService: () => sessionService, agentTasks }),
|
|
943
946
|
});
|
|
944
947
|
// Codex-style runtime: daemon routing and independent async session actors
|
|
945
948
|
// share one V8 isolate and module graph. CPU-heavy work stays in bounded
|
|
@@ -986,9 +989,16 @@ async function main() {
|
|
|
986
989
|
setAgentHomeLiveSurface({
|
|
987
990
|
busy: (sessionId) => sessionService.liveSessionBusy(sessionId),
|
|
988
991
|
notice: (sessionId, notice) => sessionService.pushLiveSessionNotice(sessionId, notice),
|
|
989
|
-
// A task session's result (session create): the agent relays it in one
|
|
990
|
-
|
|
991
|
-
|
|
992
|
+
// A task session's result (session create): the agent relays it in one
|
|
993
|
+
// turn. Resolves whether the turn was taken.
|
|
994
|
+
wake: async (sessionId, prompt, { id } = {}) =>
|
|
995
|
+
(
|
|
996
|
+
await sessionService.submitSession({
|
|
997
|
+
sessionId,
|
|
998
|
+
prompt,
|
|
999
|
+
options: { mode: 'prompt', kickoff: true, ...(id ? { id } : {}) },
|
|
1000
|
+
})
|
|
1001
|
+
)?.accepted === true,
|
|
992
1002
|
// A routine fire: one queued turn in its bot's thread (routine-home-turn.mjs).
|
|
993
1003
|
submit: async (sessionId, prompt, options) =>
|
|
994
1004
|
(await sessionService.submitSession({ sessionId, prompt, options }))?.accepted === true,
|
|
@@ -997,6 +1007,8 @@ async function main() {
|
|
|
997
1007
|
// Results queued before a restart are posted now; results a finished run
|
|
998
1008
|
// could not queue (kept on its routine's record) are queued again.
|
|
999
1009
|
recoverScheduleResults().catch((error) => log(`routine results recovery failed: ${error?.message || error}`));
|
|
1010
|
+
// Agents' sends whose report an earlier process had not queued yet.
|
|
1011
|
+
agentTasks.recover().catch((error) => log(`agent task report recovery failed: ${error?.message || error}`));
|
|
1000
1012
|
recoverRoutineDeliveries().catch((error) => log(`routine delivery recovery failed: ${error?.message || error}`));
|
|
1001
1013
|
sessionTransport = createSessionTransport({
|
|
1002
1014
|
// ctx carries the CLIENT token: the session service refcounts views across
|
|
@@ -4,6 +4,43 @@
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { createAgentChildFactory } from './agent-child.mjs';
|
|
6
6
|
import { partialHandoffTextFromSession } from '../../../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
7
|
+
import { attachmentsForIntake } from '../../../runtime/attachments/store.mjs';
|
|
8
|
+
import { artifactLines } from '../../../runtime/shared/handoff-artifacts.mjs';
|
|
9
|
+
import { collectTurnArtifacts, handoffContent, sessionRoots } from '../../../session-runtime/handoff-attachments.mjs';
|
|
10
|
+
|
|
11
|
+
/** An agent turn's prompt: text, or text with forwarded attachment parts
|
|
12
|
+
* (handoff-attachments.mjs), never any other shape. */
|
|
13
|
+
export function agentTurnContent(prompt) {
|
|
14
|
+
if (!Array.isArray(prompt)) return String(prompt || '');
|
|
15
|
+
const text = prompt
|
|
16
|
+
.filter((part) => part?.type === 'text' && !part.attachmentRef)
|
|
17
|
+
.map((part) => String(part.text || ''))
|
|
18
|
+
.join('\n');
|
|
19
|
+
return handoffContent(text, prompt);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The prompt row's metadata: a Lead's message, with the files it carries. */
|
|
23
|
+
export function agentTurnTranscriptMeta(content) {
|
|
24
|
+
const attachments = Array.isArray(content) ? attachmentsForIntake(content) : [];
|
|
25
|
+
return { sender: 'lead', ...(attachments.length ? { attachments } : {}) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The artifact lines of the turn that began at `messageStart`; [] on any
|
|
29
|
+
* failure to read it (the handoff still goes out). */
|
|
30
|
+
async function turnArtifactLines(runtime, messageStart, descriptor) {
|
|
31
|
+
try {
|
|
32
|
+
const { messages } = await runtime.readModelMessages(messageStart);
|
|
33
|
+
const session = runtime.session || descriptor;
|
|
34
|
+
const { files, omitted } = collectTurnArtifacts(messages, {
|
|
35
|
+
cwd: descriptor.cwd,
|
|
36
|
+
roots: sessionRoots(session, descriptor.cwd),
|
|
37
|
+
});
|
|
38
|
+
// Text only: the caller forwards these by path (an id in text is never trusted).
|
|
39
|
+
return artifactLines(files, omitted, { ids: false });
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
7
44
|
|
|
8
45
|
export function createAgentTurns({
|
|
9
46
|
registry,
|
|
@@ -67,15 +104,16 @@ export function createAgentTurns({
|
|
|
67
104
|
descriptor.stage = 'running';
|
|
68
105
|
descriptor.updatedAt = Date.now();
|
|
69
106
|
try {
|
|
107
|
+
const content = agentTurnContent(prompt);
|
|
70
108
|
const options = {
|
|
71
109
|
id: `agent-turn-${randomUUID()}`,
|
|
72
110
|
mode: 'prompt',
|
|
73
111
|
priority: 'next',
|
|
74
112
|
context,
|
|
75
|
-
transcriptMeta:
|
|
113
|
+
transcriptMeta: agentTurnTranscriptMeta(content),
|
|
76
114
|
...(entry.runtime.isWireSafe === true || typeof onToolResult !== 'function' ? {} : { onToolResult }),
|
|
77
115
|
};
|
|
78
|
-
const detail = await target.call(entry.runtime,
|
|
116
|
+
const detail = await target.call(entry.runtime, content, options);
|
|
79
117
|
if (detail?.status === 'failed') {
|
|
80
118
|
throw new Error(String(detail.error || 'agent session turn failed'));
|
|
81
119
|
}
|
|
@@ -91,7 +129,12 @@ export function createAgentTurns({
|
|
|
91
129
|
const reason = String(detail?.reason || detail?.error || '').trim();
|
|
92
130
|
throw new Error(`agent turn ${status}${reason ? `: ${reason}` : ''}`);
|
|
93
131
|
}
|
|
94
|
-
|
|
132
|
+
let result = detail?.result || { content: '' };
|
|
133
|
+
// The files the turn produced reach the owner with its handoff.
|
|
134
|
+
const files = await turnArtifactLines(entry.runtime, messageStart, descriptor);
|
|
135
|
+
if (files.length) {
|
|
136
|
+
result = { ...result, content: [String(result.content || ''), files.join('\n')].join('\n\n') };
|
|
137
|
+
}
|
|
95
138
|
descriptor.status = 'idle';
|
|
96
139
|
descriptor.stage = 'idle';
|
|
97
140
|
descriptor.lastHandoff = typeof result?.content === 'string' ? result.content : '';
|