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
|
@@ -166,6 +166,12 @@ function saveBuffer(buffer) {
|
|
|
166
166
|
return { attachmentRef, sizeBytes: buffer.length };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/** Store `buffer` (a file a bot or agent hands on, session-runtime/
|
|
170
|
+
* handoff-attachments.mjs) under its content address: `{ attachmentRef, sizeBytes }`. */
|
|
171
|
+
export function storeAttachmentBuffer(buffer) {
|
|
172
|
+
return saveBuffer(buffer);
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
export function isAttachmentReference(value) {
|
|
170
176
|
return Boolean(value && typeof value === 'object' && ATTACHMENT_REF_RE.test(String(value.attachmentRef || '')));
|
|
171
177
|
}
|
|
@@ -267,7 +267,7 @@ export function effortJudgeInfo(dir = effortJudgeModelDir()) {
|
|
|
267
267
|
/* not installed */
|
|
268
268
|
}
|
|
269
269
|
// The installed model names its backbone in calibration.json; older packs predate the field.
|
|
270
|
-
let backbone = '
|
|
270
|
+
let backbone = 'EmbeddingGemma 2 (18 layers)';
|
|
271
271
|
try {
|
|
272
272
|
backbone = JSON.parse(readFileSync(join(dir, 'calibration.json'), 'utf8')).backbone || backbone;
|
|
273
273
|
} catch {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release": "effort-judge-
|
|
3
|
+
"release": "effort-judge-v7",
|
|
4
4
|
"files": {
|
|
5
5
|
"model.onnx": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/model.onnx",
|
|
7
|
+
"sha256": "d01ceb1685e123335d2b70809e7b53fcc07ffb110b9412b408bc6b09a5394288",
|
|
8
8
|
"size": 153062716
|
|
9
9
|
},
|
|
10
10
|
"tokenizer.json": {
|
|
11
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
11
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/tokenizer.json",
|
|
12
12
|
"sha256": "22c064aaa468855d6211fe5c2ec8fdec595c97c55eb2ffd3b9fdf71a93b16292",
|
|
13
13
|
"size": 32170665
|
|
14
14
|
},
|
|
15
15
|
"calibration.json": {
|
|
16
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
17
|
-
"sha256": "
|
|
18
|
-
"size":
|
|
16
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v7/calibration.json",
|
|
17
|
+
"sha256": "01fe4cc99972bcca15e6ce8384cabf83c5705117e33da9a87541dc0f3d60a035",
|
|
18
|
+
"size": 223
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// An agent definition's kind (isContactAgentId reads it), decided read-only —
|
|
2
|
+
// the one rule for the runtime and the desktop (which reads `kind` from the
|
|
3
|
+
// runtime's catalog rows). Nothing is ever stamped on disk.
|
|
4
|
+
//
|
|
5
|
+
// An explicit frontmatter `kind` wins (`bot` from the bot dialog and Mixy's
|
|
6
|
+
// setup tool, `agent` from the Workflows page and other chats). v1.3.0 wrote
|
|
7
|
+
// no marker for bots, and v1.2.6 agents have none either, so an unmarked
|
|
8
|
+
// user-made agent is a bot only on evidence that only a v1.3.0 bot creation
|
|
9
|
+
// produced:
|
|
10
|
+
// T1 its home thread holds the creation greeting (agentGreetingKickoff,
|
|
11
|
+
// sent only to bots a dialog or the host greeted on creation; agents
|
|
12
|
+
// older than 1.3 were recorded without one);
|
|
13
|
+
// T2 its agent.json has an `avatar` (the bot dialog always sent one) AND
|
|
14
|
+
// its AGENT.md body is the bot dialog's default (agentBodyFromBrief).
|
|
15
|
+
// Anything else is a workflow agent, as in v1.2.6.
|
|
16
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { agentHomeSessionId } from '../agent/orchestrator/session/agent-home.mjs';
|
|
19
|
+
import { readMarkdownDocument } from './markdown-frontmatter.mjs';
|
|
20
|
+
import { BOT_AGENT_KIND } from './agent-route-config.mjs';
|
|
21
|
+
import { botStoreActive, isStoreBot } from './agent-store.mjs';
|
|
22
|
+
|
|
23
|
+
const RUNTIME_MARKER = '[mixdog-runtime]';
|
|
24
|
+
/** The creation greeting's opening (shared/agent-greeting.ts, v1.3.0 on). */
|
|
25
|
+
const CREATION_GREETING = /(^|\n)\[mixdog-runtime\] The user just created you as /;
|
|
26
|
+
/** The closing lines of the bot dialog's default AGENT.md (agentBodyFromBrief). */
|
|
27
|
+
const DIALOG_BODY_TAIL = [
|
|
28
|
+
/^- Help the user( with this role)? in a natural, friendly conversation, in the language they write in\.$/,
|
|
29
|
+
/^- Ask a short question when a request is unclear, and say plainly when something is outside what you do\.$/,
|
|
30
|
+
/^- Keep replies as short or as detailed as the moment needs\.$/,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function messageText(content) {
|
|
34
|
+
if (typeof content === 'string') return content;
|
|
35
|
+
if (!Array.isArray(content)) return '';
|
|
36
|
+
return content
|
|
37
|
+
.map((part) => (typeof part === 'string' ? part : typeof part?.text === 'string' ? part.text : ''))
|
|
38
|
+
.join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const NO_HOME = Object.freeze({ exists: false, greeted: false, chatted: false });
|
|
42
|
+
// Per session file, keyed by its size and mtime: a home thread is read once
|
|
43
|
+
// until it changes.
|
|
44
|
+
const homeEvidenceCache = new Map();
|
|
45
|
+
|
|
46
|
+
/** What `agentId`'s home thread (sessions/sess_home_<id>.json) holds:
|
|
47
|
+
* whether it exists, whether it has the creation greeting, and whether the
|
|
48
|
+
* user and the agent talked (any user message that is not a runtime turn,
|
|
49
|
+
* or any assistant message). A missing or unreadable file holds nothing. */
|
|
50
|
+
export function homeThreadEvidence(dataDir, agentId) {
|
|
51
|
+
const sessionId = agentHomeSessionId(agentId);
|
|
52
|
+
if (!dataDir || !sessionId) return NO_HOME;
|
|
53
|
+
const file = join(dataDir, 'sessions', `${sessionId}.json`);
|
|
54
|
+
let stat;
|
|
55
|
+
try {
|
|
56
|
+
stat = statSync(file);
|
|
57
|
+
} catch {
|
|
58
|
+
return NO_HOME;
|
|
59
|
+
}
|
|
60
|
+
const cached = homeEvidenceCache.get(file);
|
|
61
|
+
if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) return cached.value;
|
|
62
|
+
let messages = [];
|
|
63
|
+
try {
|
|
64
|
+
const doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
65
|
+
if (Array.isArray(doc?.messages)) messages = doc.messages;
|
|
66
|
+
} catch {
|
|
67
|
+
// unreadable: it proves nothing
|
|
68
|
+
}
|
|
69
|
+
let greeted = false;
|
|
70
|
+
let chatted = false;
|
|
71
|
+
for (const message of messages) {
|
|
72
|
+
if (message?.role === 'assistant') chatted = true;
|
|
73
|
+
if (message?.role !== 'user') continue;
|
|
74
|
+
const text = messageText(message.content);
|
|
75
|
+
if (CREATION_GREETING.test(text)) greeted = true;
|
|
76
|
+
else if (text.trim() && !text.trimStart().startsWith(RUNTIME_MARKER)) chatted = true;
|
|
77
|
+
}
|
|
78
|
+
const value = Object.freeze({ exists: true, greeted, chatted });
|
|
79
|
+
homeEvidenceCache.set(file, { size: stat.size, mtimeMs: stat.mtimeMs, value });
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isDialogDefaultBody(body) {
|
|
84
|
+
const lines = String(body || '')
|
|
85
|
+
.trim()
|
|
86
|
+
.split(/\r?\n/);
|
|
87
|
+
const tail = lines.slice(-DIALOG_BODY_TAIL.length);
|
|
88
|
+
return tail.length === DIALOG_BODY_TAIL.length && DIALOG_BODY_TAIL.every((line, index) => line.test(tail[index]));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The definition's kind: the explicit frontmatter kind (lower-cased); else,
|
|
92
|
+
* for an unmarked user-made agent (`dataDir`/`id` given, not a shipped role),
|
|
93
|
+
* `bot` on T1 or T2 evidence; else null (a workflow agent). `manifest` and
|
|
94
|
+
* `body` are the user copy's agent.json and AGENT.md body. */
|
|
95
|
+
export function agentDefinitionKind(definition) {
|
|
96
|
+
const { dataDir, id } = definition;
|
|
97
|
+
// Once bots have their own store the folder decides: bots/ holds bots,
|
|
98
|
+
// agents/ workflow agents (an explicit marker still read as written).
|
|
99
|
+
if (dataDir && id && isStoreBot(dataDir, id)) return BOT_AGENT_KIND;
|
|
100
|
+
if (dataDir && botStoreActive(dataDir)) return explicitKind(definition.frontmatterKind);
|
|
101
|
+
return evidenceKind(definition);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const explicitKind = (frontmatterKind) =>
|
|
105
|
+
(typeof frontmatterKind === 'string' ? frontmatterKind.trim().toLowerCase() : '') || null;
|
|
106
|
+
|
|
107
|
+
/** The pre-store rule alone (the split's decision input): the explicit kind,
|
|
108
|
+
* else `bot` on T1/T2 evidence for an unmarked user-made agent, else null. */
|
|
109
|
+
export function evidenceKind({ frontmatterKind, dataDir, shippedRoot, id, manifest, body }) {
|
|
110
|
+
const explicit = explicitKind(frontmatterKind);
|
|
111
|
+
if (explicit) return explicit;
|
|
112
|
+
if (!dataDir || !id || id === 'lead') return null;
|
|
113
|
+
if (shippedRoot && existsSync(join(shippedRoot, 'agents', id, 'AGENT.md'))) return null;
|
|
114
|
+
if (manifest?.avatar && isDialogDefaultBody(body)) return BOT_AGENT_KIND;
|
|
115
|
+
return homeThreadEvidence(dataDir, id).greeted ? BOT_AGENT_KIND : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** agentDefinitionKind from the user copy on disk, for readers that have no
|
|
119
|
+
* loaded definition at hand: a store bot (bots/<id>/) is a bot; any other id
|
|
120
|
+
* is read from agents/<id>/, else from the shipped role. */
|
|
121
|
+
export function readAgentDefinitionKind({ dataDir, shippedRoot, id }) {
|
|
122
|
+
if (isStoreBot(dataDir, id)) return BOT_AGENT_KIND;
|
|
123
|
+
const dir = join(dataDir, 'agents', id);
|
|
124
|
+
let doc;
|
|
125
|
+
try {
|
|
126
|
+
doc = readMarkdownDocument(readFileSync(join(dir, 'AGENT.md'), 'utf8'));
|
|
127
|
+
} catch {
|
|
128
|
+
// No user copy: the shipped role's own frontmatter decides.
|
|
129
|
+
try {
|
|
130
|
+
return agentDefinitionKind({
|
|
131
|
+
frontmatterKind: readMarkdownDocument(readFileSync(join(shippedRoot, 'agents', id, 'AGENT.md'), 'utf8'))
|
|
132
|
+
.frontmatter.kind,
|
|
133
|
+
});
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
let manifest = {};
|
|
139
|
+
try {
|
|
140
|
+
manifest = JSON.parse(readFileSync(join(dir, 'agent.json'), 'utf8')) || {};
|
|
141
|
+
} catch {
|
|
142
|
+
manifest = {};
|
|
143
|
+
}
|
|
144
|
+
return agentDefinitionKind({
|
|
145
|
+
frontmatterKind: doc.frontmatter.kind,
|
|
146
|
+
dataDir,
|
|
147
|
+
shippedRoot,
|
|
148
|
+
id,
|
|
149
|
+
manifest,
|
|
150
|
+
body: doc.body,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -20,16 +20,20 @@ export const DEFAULT_DISABLED_AGENT_ROSTER = Object.freeze([...DEFAULT_DISABLED_
|
|
|
20
20
|
* an agent: a workflow delegation role, never a contact (봇). */
|
|
21
21
|
export const WORKFLOW_AGENT_KIND = 'agent';
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
23
|
+
/** The AGENT.md frontmatter `kind` every bot creation path writes: the bot
|
|
24
|
+
* dialog, and the setup tool when Mixy (agent mode) creates an agent. */
|
|
25
|
+
export const BOT_AGENT_KIND = 'bot';
|
|
26
|
+
|
|
27
|
+
/** A contact agent (a 봇): one explicitly marked `kind: bot` at creation —
|
|
28
|
+
* never Mixy ('lead') nor a shipped role (the starter sub-agents Worker,
|
|
29
|
+
* Reviewer, … and the Maintainer). `kind` is the definition's frontmatter
|
|
30
|
+
* `kind`; an unmarked custom agent (every agent made before bots existed)
|
|
31
|
+
* stays a workflow agent. The one rule for the runtime (delegation, agent
|
|
32
|
+
* catalogs) and the desktop (contacts, agent lists). */
|
|
29
33
|
export function isContactAgentId(value, kind) {
|
|
30
34
|
const id = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
31
|
-
const
|
|
32
|
-
return Boolean(id) && id !== 'lead' && !DEFAULT_DISABLED_AGENT_ROSTER.includes(id) &&
|
|
35
|
+
const bot = typeof kind === 'string' && kind.trim().toLowerCase() === BOT_AGENT_KIND;
|
|
36
|
+
return Boolean(id) && id !== 'lead' && !DEFAULT_DISABLED_AGENT_ROSTER.includes(id) && bot;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
function record(value) {
|
|
@@ -128,9 +132,19 @@ export function withAgentDisabled(config, agentId, disabled) {
|
|
|
128
132
|
// left under retired slots (agents.maintenance, workflowRoutes.*,
|
|
129
133
|
// maintenance.memory, generated workflow-* presets) are scrubbed by
|
|
130
134
|
// canonicalizeAgentRouteStorage, never folded in.
|
|
135
|
+
// A bot's route lives with the bot (agent-store.mjs registers the reader in
|
|
136
|
+
// the runtime): the resolver answers a route, null (a bot without one), or
|
|
137
|
+
// undefined (no bot — the config decides).
|
|
138
|
+
let botRouteResolver = null;
|
|
139
|
+
export function setBotRouteResolver(resolver) {
|
|
140
|
+
botRouteResolver = typeof resolver === 'function' ? resolver : null;
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
export function configuredAgentRouteCandidates(config, agentId) {
|
|
132
144
|
const id = String(agentId || '').trim();
|
|
133
145
|
if (!id) return [];
|
|
146
|
+
const bot = botRouteResolver?.(id);
|
|
147
|
+
if (bot !== undefined) return bot ? [bot] : [];
|
|
134
148
|
const route = record(config?.agents)[id];
|
|
135
149
|
return route ? [route] : [];
|
|
136
150
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Where a user-made definition lives once bots have their own store (1.3.1):
|
|
2
|
+
// bots under <dataDir>/bots/<id>/ (with their own model route in agent.json),
|
|
3
|
+
// workflow agents under <dataDir>/agents/<id>/. The store is active once the
|
|
4
|
+
// one-time split finished (bots/migration.json, bot-split-migration.mjs);
|
|
5
|
+
// before that — or if it never could run — every reader sees agents/ as
|
|
6
|
+
// before. The ids the split copied are bots for good: their agents/ originals
|
|
7
|
+
// stay on disk for rollback but no catalog reads them again.
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { setBotRouteResolver } from './agent-route-config.mjs';
|
|
11
|
+
|
|
12
|
+
export const BOTS_DIR = 'bots';
|
|
13
|
+
export const BOT_MIGRATION_MANIFEST = 'migration.json';
|
|
14
|
+
export const BOT_MIGRATION_VERSION = 1;
|
|
15
|
+
/** Mixy's bot id once it has its own folder. */
|
|
16
|
+
export const MIXY_BOT_ID = 'mixy';
|
|
17
|
+
|
|
18
|
+
function readJson(file) {
|
|
19
|
+
try {
|
|
20
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
21
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Small stat-keyed caches: the store is read on route and catalog paths.
|
|
28
|
+
const fileCache = new Map();
|
|
29
|
+
function cachedJson(file) {
|
|
30
|
+
let stat;
|
|
31
|
+
try {
|
|
32
|
+
stat = statSync(file);
|
|
33
|
+
} catch {
|
|
34
|
+
fileCache.delete(file);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const hit = fileCache.get(file);
|
|
38
|
+
if (hit && hit.mtimeMs === stat.mtimeMs && hit.size === stat.size) return hit.value;
|
|
39
|
+
const value = readJson(file);
|
|
40
|
+
fileCache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, value });
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The finished split's record, or null before it ran. */
|
|
45
|
+
export function readBotMigration(dataDir) {
|
|
46
|
+
if (!dataDir) return null;
|
|
47
|
+
const manifest = cachedJson(join(dataDir, BOTS_DIR, BOT_MIGRATION_MANIFEST));
|
|
48
|
+
return manifest?.version === BOT_MIGRATION_VERSION ? manifest : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether `dataDir` has its bot store (the split finished). */
|
|
52
|
+
export function botStoreActive(dataDir) {
|
|
53
|
+
return readBotMigration(dataDir) !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The agents/ ids the split copied to bots/: the agent catalog skips them. */
|
|
57
|
+
export function migratedBotIds(dataDir) {
|
|
58
|
+
return new Set(readBotMigration(dataDir)?.bots || []);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const botDir = (dataDir, id) => join(dataDir, BOTS_DIR, id);
|
|
62
|
+
|
|
63
|
+
/** Whether `id` is a store bot: the store is active and the bot has its
|
|
64
|
+
* folder, or the split copied it (an id it moved is never an agent again).
|
|
65
|
+
* Mixy's own folder is no ordinary bot. */
|
|
66
|
+
export function isStoreBot(dataDir, id) {
|
|
67
|
+
if (!dataDir || !id || id === MIXY_BOT_ID || id.startsWith('.')) return false;
|
|
68
|
+
const manifest = readBotMigration(dataDir);
|
|
69
|
+
if (!manifest) return false;
|
|
70
|
+
return existsSync(botDir(dataDir, id)) || (manifest.bots || []).includes(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The folder of a user-made definition: bots/<id> for a store bot, else agents/<id>. */
|
|
74
|
+
export function userDefinitionDir(dataDir, id) {
|
|
75
|
+
return isStoreBot(dataDir, id) ? botDir(dataDir, id) : join(dataDir, 'agents', id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const isCompleteRoute = (route) =>
|
|
79
|
+
Boolean(
|
|
80
|
+
route && typeof route === 'object' && String(route.provider || '').trim() && String(route.model || '').trim()
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/** A store bot's own route (agent.json `route`): the route, null when it has
|
|
84
|
+
* none, undefined when `id` is no store bot. */
|
|
85
|
+
export function readBotRoute(dataDir, id) {
|
|
86
|
+
if (!isStoreBot(dataDir, id)) return undefined;
|
|
87
|
+
const route = cachedJson(join(botDir(dataDir, id), 'agent.json'))?.route;
|
|
88
|
+
return isCompleteRoute(route) ? route : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Stores (or with null removes) a store bot's route in its agent.json; every
|
|
92
|
+
* other key is kept. */
|
|
93
|
+
export function writeBotRoute(dataDir, id, route) {
|
|
94
|
+
const dir = botDir(dataDir, id);
|
|
95
|
+
const file = join(dir, 'agent.json');
|
|
96
|
+
const manifest = readJson(file) || {};
|
|
97
|
+
if (route) manifest.route = route;
|
|
98
|
+
else delete manifest.route;
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
101
|
+
fileCache.delete(file);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Mixy: its own bot (bots/mixy), apart from the Lead ---
|
|
105
|
+
// Mixy's identity (name, face, persona, 일하는 방식, connectors, `created`)
|
|
106
|
+
// lives in bots/mixy once the store is active, in agents/lead before. Its
|
|
107
|
+
// model route is always its own (bots/mixy/agent.json `route`), never the
|
|
108
|
+
// Lead's: changing one never changes the other. Its home thread keeps the id
|
|
109
|
+
// sess_home_lead, and its runtime role id stays `lead`.
|
|
110
|
+
|
|
111
|
+
/** The folder Mixy's identity is read from and written to. */
|
|
112
|
+
export function mixyIdentityDir(dataDir) {
|
|
113
|
+
return botStoreActive(dataDir) ? botDir(dataDir, MIXY_BOT_ID) : join(dataDir, 'agents', 'lead');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Mixy's own route, or null when it has none (its thread keeps its own). */
|
|
117
|
+
export function readMixyRoute(dataDir) {
|
|
118
|
+
if (!dataDir) return null;
|
|
119
|
+
const route = cachedJson(join(botDir(dataDir, MIXY_BOT_ID), 'agent.json'))?.route;
|
|
120
|
+
return isCompleteRoute(route) ? route : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Stores (or with null removes) Mixy's own route; nothing of the Lead's. */
|
|
124
|
+
export function writeMixyRoute(dataDir, route) {
|
|
125
|
+
writeBotRoute(dataDir, MIXY_BOT_ID, route);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Every config route read (configuredAgentRouteCandidates) asks the bot
|
|
129
|
+
* store first: a bot's route lives with the bot, never in the config. */
|
|
130
|
+
export function registerBotRouteStore(getDataDir) {
|
|
131
|
+
setBotRouteResolver((id) => readBotRoute(getDataDir(), id));
|
|
132
|
+
}
|