@markusylisiurunen/tau 0.2.62 → 0.2.64
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/README.md +1 -1
- package/dist/core/async/cli.js +68 -104
- package/dist/core/async/cli.js.map +1 -1
- package/dist/core/async/http_protocol.js +119 -8
- package/dist/core/async/http_protocol.js.map +1 -1
- package/dist/core/async/http_server.js +120 -240
- package/dist/core/async/http_server.js.map +1 -1
- package/dist/core/async/index.js +1 -1
- package/dist/core/async/index.js.map +1 -1
- package/dist/core/async/server_config.js +163 -341
- package/dist/core/async/server_config.js.map +1 -1
- package/dist/core/async/telegram.js +191 -133
- package/dist/core/async/telegram.js.map +1 -1
- package/dist/core/cli.js +25 -31
- package/dist/core/cli.js.map +1 -1
- package/dist/core/config/content_loader.js +57 -205
- package/dist/core/config/content_loader.js.map +1 -1
- package/dist/core/config/markdown_frontmatter.js +34 -0
- package/dist/core/config/markdown_frontmatter.js.map +1 -0
- package/dist/core/config/runtime.js +6 -1
- package/dist/core/config/runtime.js.map +1 -1
- package/dist/core/config/schema.js +268 -327
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/skill_parser.js +8 -32
- package/dist/core/config/skill_parser.js.map +1 -1
- package/dist/core/config/skills_loader.js +32 -18
- package/dist/core/config/skills_loader.js.map +1 -1
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/events/parser.js +115 -0
- package/dist/core/events/parser.js.map +1 -0
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/modes/rpc_protocol.js +249 -189
- package/dist/core/modes/rpc_protocol.js.map +1 -1
- package/dist/core/persona_reference.js +27 -0
- package/dist/core/persona_reference.js.map +1 -0
- package/dist/core/tools/send_input_to_agent.js +7 -12
- package/dist/core/tools/send_input_to_agent.js.map +1 -1
- package/dist/core/tools/spawn_agent.js +13 -14
- package/dist/core/tools/spawn_agent.js.map +1 -1
- package/dist/core/tools/terminate_agent.js +6 -11
- package/dist/core/tools/terminate_agent.js.map +1 -1
- package/dist/core/tools/wait_for_agent.js +6 -11
- package/dist/core/tools/wait_for_agent.js.map +1 -1
- package/dist/core/utils/mistral_transcription.js +13 -21
- package/dist/core/utils/mistral_transcription.js.map +1 -1
- package/dist/core/utils/parallel_api.js +15 -19
- package/dist/core/utils/parallel_api.js.map +1 -1
- package/dist/core/utils/zod.js +7 -0
- package/dist/core/utils/zod.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/main.js +21 -3
- package/dist/main.js.map +1 -1
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/chat_controller/session_maintenance_service.js +98 -171
- package/dist/tui/chat_controller/session_maintenance_service.js.map +1 -1
- package/dist/tui/chat_controller.js +48 -10
- package/dist/tui/chat_controller.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { parseMarkdownFrontMatter } from "../config/markdown_frontmatter.js";
|
|
5
|
+
import { formatPersonaReference, parsePersonaReference } from "../persona_reference.js";
|
|
6
|
+
import { REASONING_LEVELS } from "../types.js";
|
|
4
7
|
import { parseCronSchedule } from "./cron.js";
|
|
5
8
|
export class AsyncDaemonConfigError extends Error {
|
|
6
9
|
constructor(message) {
|
|
@@ -8,147 +11,117 @@ export class AsyncDaemonConfigError extends Error {
|
|
|
8
11
|
this.name = "AsyncDaemonConfigError";
|
|
9
12
|
}
|
|
10
13
|
}
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const CRON_JOB_FRONTMATTER_KEYS = new Set(["id", "projectId", "schedule", "enabled"]);
|
|
15
|
+
const nonEmptyStringSchema = z.string().trim().min(1, "must be a non-empty string.");
|
|
16
|
+
const positiveIntegerSchema = z
|
|
17
|
+
.number()
|
|
18
|
+
.int("must be a positive integer.")
|
|
19
|
+
.positive("must be a positive integer.");
|
|
20
|
+
const asyncIdListSchema = z.array(z.number().int(), {
|
|
21
|
+
message: "must be an array of integers.",
|
|
22
|
+
});
|
|
23
|
+
const asyncStringListSchema = z.array(nonEmptyStringSchema, {
|
|
24
|
+
message: "must be an array of non-empty strings.",
|
|
25
|
+
});
|
|
26
|
+
const asyncDaemonCronSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
systemMessage: nonEmptyStringSchema.optional(),
|
|
29
|
+
jobsDir: nonEmptyStringSchema.optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
const asyncDaemonTopLevelSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
host: nonEmptyStringSchema.optional(),
|
|
35
|
+
port: z
|
|
36
|
+
.number()
|
|
37
|
+
.int("must be a positive integer <= 65535.")
|
|
38
|
+
.positive("must be a positive integer <= 65535.")
|
|
39
|
+
.max(65535, "must be a positive integer <= 65535.")
|
|
40
|
+
.optional(),
|
|
41
|
+
authToken: nonEmptyStringSchema.optional(),
|
|
42
|
+
maxSessions: positiveIntegerSchema.optional(),
|
|
43
|
+
workspaceRoot: nonEmptyStringSchema.optional(),
|
|
44
|
+
systemMessage: nonEmptyStringSchema.optional(),
|
|
45
|
+
telegram: z.unknown().optional(),
|
|
46
|
+
cron: z.unknown().optional(),
|
|
47
|
+
projects: z.unknown().optional(),
|
|
48
|
+
})
|
|
49
|
+
.strict();
|
|
50
|
+
const telegramBotSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
botToken: nonEmptyStringSchema,
|
|
53
|
+
allowedProjectIds: asyncStringListSchema.min(1, "must not be empty.").optional(),
|
|
54
|
+
allowedUserIds: asyncIdListSchema.optional(),
|
|
55
|
+
allowedChatIds: asyncIdListSchema.optional(),
|
|
56
|
+
defaultProjectId: nonEmptyStringSchema.optional(),
|
|
57
|
+
systemMessage: nonEmptyStringSchema.optional(),
|
|
58
|
+
pollIntervalMs: positiveIntegerSchema.optional(),
|
|
59
|
+
requestTimeoutSeconds: positiveIntegerSchema.optional(),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
function createProjectSchema(configDir) {
|
|
63
|
+
return z
|
|
64
|
+
.object({
|
|
65
|
+
repo: nonEmptyStringSchema.refine((value) => isGithubRepoRef(value), {
|
|
66
|
+
message: "must be in owner/repo format (GitHub).",
|
|
67
|
+
}),
|
|
68
|
+
ref: nonEmptyStringSchema.optional(),
|
|
69
|
+
workspaceRoot: nonEmptyStringSchema
|
|
70
|
+
.transform((value) => resolve(configDir, value))
|
|
71
|
+
.optional(),
|
|
72
|
+
workingDirectory: nonEmptyStringSchema
|
|
73
|
+
.refine((value) => !isAbsolute(value), {
|
|
74
|
+
message: "must be a relative path.",
|
|
75
|
+
})
|
|
76
|
+
.optional(),
|
|
77
|
+
description: nonEmptyStringSchema.optional(),
|
|
78
|
+
bootstrapCommands: z
|
|
79
|
+
.array(z.string().refine((value) => value.trim().length > 0), {
|
|
80
|
+
message: "must be a non-empty string array.",
|
|
81
|
+
})
|
|
82
|
+
.min(1, "must be a non-empty string array.")
|
|
83
|
+
.optional(),
|
|
84
|
+
backgroundBootstrapCommands: z
|
|
85
|
+
.array(z.string().refine((value) => value.trim().length > 0), {
|
|
86
|
+
message: "must be a non-empty string array.",
|
|
87
|
+
})
|
|
88
|
+
.min(1, "must be a non-empty string array.")
|
|
89
|
+
.optional(),
|
|
90
|
+
persona: z.string().optional(),
|
|
91
|
+
riskLevel: z.enum(["read-only", "read-write"]).optional(),
|
|
92
|
+
sandbox: z.boolean().optional(),
|
|
93
|
+
noAgentContextFiles: z.boolean().optional(),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
13
96
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
97
|
+
function formatUnknownKeysError(sourceLabel, fieldPath, keys) {
|
|
98
|
+
const unknownKeys = [...keys].sort();
|
|
99
|
+
const keyLabel = unknownKeys.length === 1 ? "key" : "keys";
|
|
100
|
+
return `${sourceLabel}: unknown ${keyLabel} in ${fieldPath}: ${unknownKeys.join(", ")}.`;
|
|
16
101
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
24
|
-
if (lines[i]?.trim() === "---") {
|
|
25
|
-
endIndex = i;
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (endIndex === -1) {
|
|
30
|
-
return { frontMatter: {}, body: content.trim() };
|
|
31
|
-
}
|
|
32
|
-
const frontMatterLines = lines.slice(1, endIndex);
|
|
33
|
-
const bodyLines = lines.slice(endIndex + 1);
|
|
34
|
-
return {
|
|
35
|
-
frontMatter: parseYamlFrontMatter(frontMatterLines.join("\n")),
|
|
36
|
-
body: bodyLines.join("\n").trim(),
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function parseYamlFrontMatter(yamlText) {
|
|
40
|
-
try {
|
|
41
|
-
const parsed = parseYaml(yamlText);
|
|
42
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
43
|
-
return {};
|
|
44
|
-
}
|
|
45
|
-
return parsed;
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return {};
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function parseAsyncIdList(raw, fieldPath, sourceLabel) {
|
|
52
|
-
if (!Array.isArray(raw)) {
|
|
53
|
-
return { errors: [`${sourceLabel}: ${fieldPath} must be an array of integers.`] };
|
|
54
|
-
}
|
|
55
|
-
const values = [];
|
|
56
|
-
for (const entry of raw) {
|
|
57
|
-
if (typeof entry !== "number" || !Number.isFinite(entry) || !Number.isInteger(entry)) {
|
|
58
|
-
return { errors: [`${sourceLabel}: ${fieldPath} must be an array of integers.`] };
|
|
59
|
-
}
|
|
60
|
-
values.push(entry);
|
|
61
|
-
}
|
|
62
|
-
return { values, errors: [] };
|
|
63
|
-
}
|
|
64
|
-
function parseAsyncStringList(raw, fieldPath, sourceLabel) {
|
|
65
|
-
if (!Array.isArray(raw)) {
|
|
66
|
-
return { errors: [`${sourceLabel}: ${fieldPath} must be an array of non-empty strings.`] };
|
|
67
|
-
}
|
|
68
|
-
const values = [];
|
|
69
|
-
for (const entry of raw) {
|
|
70
|
-
if (typeof entry !== "string" || !entry.trim()) {
|
|
71
|
-
return { errors: [`${sourceLabel}: ${fieldPath} must be an array of non-empty strings.`] };
|
|
102
|
+
function formatSectionZodErrors(error, sourceLabel, fieldPath) {
|
|
103
|
+
const errors = [];
|
|
104
|
+
for (const issue of error.issues) {
|
|
105
|
+
if (issue.code === "unrecognized_keys") {
|
|
106
|
+
errors.push(formatUnknownKeysError(sourceLabel, fieldPath, issue.keys));
|
|
107
|
+
continue;
|
|
72
108
|
}
|
|
73
|
-
|
|
109
|
+
const issuePath = issue.path.length > 0 ? `.${issue.path.join(".")}` : "";
|
|
110
|
+
errors.push(`${sourceLabel}: ${fieldPath}${issuePath} ${issue.message}`);
|
|
74
111
|
}
|
|
75
|
-
return
|
|
112
|
+
return errors;
|
|
76
113
|
}
|
|
77
114
|
function parseTelegramBotConfig(raw, fieldPath, sourceLabel, knownProjectIds) {
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
const parsed = telegramBotSchema.safeParse(raw);
|
|
116
|
+
if (!parsed.success) {
|
|
117
|
+
return { errors: formatSectionZodErrors(parsed.error, sourceLabel, fieldPath) };
|
|
80
118
|
}
|
|
81
|
-
const
|
|
82
|
-
const config = {};
|
|
119
|
+
const config = parsed.data;
|
|
83
120
|
const errors = [];
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
errors.push(`${sourceLabel}: ${fieldPath}.botToken must be a non-empty string.`);
|
|
89
|
-
}
|
|
90
|
-
if (data.allowedProjectIds !== undefined) {
|
|
91
|
-
const parsed = parseAsyncStringList(data.allowedProjectIds, `${fieldPath}.allowedProjectIds`, sourceLabel);
|
|
92
|
-
if (parsed.values) {
|
|
93
|
-
if (parsed.values.length === 0) {
|
|
94
|
-
errors.push(`${sourceLabel}: ${fieldPath}.allowedProjectIds must not be empty.`);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
const missingProjectIds = parsed.values.filter((projectId) => !knownProjectIds.has(projectId));
|
|
98
|
-
if (missingProjectIds.length > 0) {
|
|
99
|
-
errors.push(`${sourceLabel}: ${fieldPath}.allowedProjectIds contains unknown project ids: ${missingProjectIds.join(", ")}`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
config.allowedProjectIds = parsed.values;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
errors.push(...parsed.errors);
|
|
107
|
-
}
|
|
108
|
-
if (data.allowedUserIds !== undefined) {
|
|
109
|
-
const parsed = parseAsyncIdList(data.allowedUserIds, `${fieldPath}.allowedUserIds`, sourceLabel);
|
|
110
|
-
if (parsed.values) {
|
|
111
|
-
config.allowedUserIds = parsed.values;
|
|
112
|
-
}
|
|
113
|
-
errors.push(...parsed.errors);
|
|
114
|
-
}
|
|
115
|
-
if (data.allowedChatIds !== undefined) {
|
|
116
|
-
const parsed = parseAsyncIdList(data.allowedChatIds, `${fieldPath}.allowedChatIds`, sourceLabel);
|
|
117
|
-
if (parsed.values) {
|
|
118
|
-
config.allowedChatIds = parsed.values;
|
|
119
|
-
}
|
|
120
|
-
errors.push(...parsed.errors);
|
|
121
|
-
}
|
|
122
|
-
if (data.defaultProjectId !== undefined) {
|
|
123
|
-
if (typeof data.defaultProjectId === "string" && data.defaultProjectId.trim()) {
|
|
124
|
-
config.defaultProjectId = data.defaultProjectId.trim();
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
errors.push(`${sourceLabel}: ${fieldPath}.defaultProjectId must be a non-empty string.`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (data.systemMessage !== undefined) {
|
|
131
|
-
if (typeof data.systemMessage === "string" && data.systemMessage.trim()) {
|
|
132
|
-
config.systemMessage = data.systemMessage.trim();
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
errors.push(`${sourceLabel}: ${fieldPath}.systemMessage must be a non-empty string.`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (data.pollIntervalMs !== undefined) {
|
|
139
|
-
if (isPositiveInteger(data.pollIntervalMs)) {
|
|
140
|
-
config.pollIntervalMs = data.pollIntervalMs;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
errors.push(`${sourceLabel}: ${fieldPath}.pollIntervalMs must be a positive integer.`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (data.requestTimeoutSeconds !== undefined) {
|
|
147
|
-
if (isPositiveInteger(data.requestTimeoutSeconds)) {
|
|
148
|
-
config.requestTimeoutSeconds = data.requestTimeoutSeconds;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
errors.push(`${sourceLabel}: ${fieldPath}.requestTimeoutSeconds must be a positive integer.`);
|
|
121
|
+
if (config.allowedProjectIds) {
|
|
122
|
+
const missingProjectIds = config.allowedProjectIds.filter((projectId) => !knownProjectIds.has(projectId));
|
|
123
|
+
if (missingProjectIds.length > 0) {
|
|
124
|
+
errors.push(`${sourceLabel}: ${fieldPath}.allowedProjectIds contains unknown project ids: ${missingProjectIds.join(", ")}`);
|
|
152
125
|
}
|
|
153
126
|
}
|
|
154
127
|
if (config.defaultProjectId && !knownProjectIds.has(config.defaultProjectId)) {
|
|
@@ -159,19 +132,20 @@ function parseTelegramBotConfig(raw, fieldPath, sourceLabel, knownProjectIds) {
|
|
|
159
132
|
!config.allowedProjectIds.includes(config.defaultProjectId)) {
|
|
160
133
|
errors.push(`${sourceLabel}: ${fieldPath}.defaultProjectId must be included in ${fieldPath}.allowedProjectIds`);
|
|
161
134
|
}
|
|
162
|
-
if (
|
|
135
|
+
if (errors.length > 0) {
|
|
163
136
|
return { errors };
|
|
164
137
|
}
|
|
165
|
-
return { config, errors };
|
|
138
|
+
return { config, errors: [] };
|
|
166
139
|
}
|
|
167
140
|
function parseTelegramConfig(raw, sourceLabel, knownProjectIds) {
|
|
168
141
|
if (raw === undefined) {
|
|
169
142
|
return { errors: [] };
|
|
170
143
|
}
|
|
171
|
-
|
|
144
|
+
const parsedObject = z.record(z.string(), z.unknown()).safeParse(raw);
|
|
145
|
+
if (!parsedObject.success) {
|
|
172
146
|
return { errors: [`${sourceLabel}: telegram must be an object.`] };
|
|
173
147
|
}
|
|
174
|
-
const entries = Object.entries(
|
|
148
|
+
const entries = Object.entries(parsedObject.data);
|
|
175
149
|
if (entries.length === 0) {
|
|
176
150
|
return { errors: [`${sourceLabel}: telegram must define at least one bot id.`] };
|
|
177
151
|
}
|
|
@@ -197,164 +171,43 @@ function parseCronConfig(raw, sourceLabel) {
|
|
|
197
171
|
if (raw === undefined) {
|
|
198
172
|
return { errors: [] };
|
|
199
173
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const data = raw;
|
|
204
|
-
const config = {};
|
|
205
|
-
const errors = [];
|
|
206
|
-
if (data.systemMessage !== undefined) {
|
|
207
|
-
if (typeof data.systemMessage === "string" && data.systemMessage.trim()) {
|
|
208
|
-
config.systemMessage = data.systemMessage.trim();
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
errors.push(`${sourceLabel}: cron.systemMessage must be a non-empty string.`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (data.jobsDir !== undefined) {
|
|
215
|
-
if (typeof data.jobsDir === "string" && data.jobsDir.trim()) {
|
|
216
|
-
config.jobsDir = data.jobsDir.trim();
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
errors.push(`${sourceLabel}: cron.jobsDir must be a non-empty string.`);
|
|
220
|
-
}
|
|
174
|
+
const parsed = asyncDaemonCronSchema.safeParse(raw);
|
|
175
|
+
if (!parsed.success) {
|
|
176
|
+
return { errors: formatSectionZodErrors(parsed.error, sourceLabel, "cron") };
|
|
221
177
|
}
|
|
178
|
+
const config = parsed.data;
|
|
222
179
|
if (Object.keys(config).length === 0) {
|
|
223
|
-
return { errors };
|
|
224
|
-
}
|
|
225
|
-
return { config, errors };
|
|
226
|
-
}
|
|
227
|
-
function parseRiskLevel(raw) {
|
|
228
|
-
if (raw === "read-only" || raw === "read-write") {
|
|
229
|
-
return raw;
|
|
180
|
+
return { errors: [] };
|
|
230
181
|
}
|
|
231
|
-
return
|
|
182
|
+
return { config, errors: [] };
|
|
232
183
|
}
|
|
233
184
|
function isGithubRepoRef(value) {
|
|
234
185
|
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value);
|
|
235
186
|
}
|
|
236
187
|
function parseProject(raw, sourceLabel, projectId, configDir) {
|
|
237
|
-
|
|
238
|
-
|
|
188
|
+
const schema = createProjectSchema(configDir);
|
|
189
|
+
const parsed = schema.safeParse(raw);
|
|
190
|
+
if (!parsed.success) {
|
|
191
|
+
return { errors: formatSectionZodErrors(parsed.error, sourceLabel, `projects.${projectId}`) };
|
|
239
192
|
}
|
|
240
|
-
const
|
|
193
|
+
const config = parsed.data;
|
|
241
194
|
const errors = [];
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
else if (!isGithubRepoRef(repoRaw.trim())) {
|
|
247
|
-
errors.push(`${sourceLabel}: projects.${projectId}.repo must be in owner/repo format (GitHub).`);
|
|
248
|
-
}
|
|
249
|
-
const config = {
|
|
250
|
-
repo: typeof repoRaw === "string" ? repoRaw.trim() : "",
|
|
251
|
-
};
|
|
252
|
-
if (data.ref !== undefined) {
|
|
253
|
-
if (typeof data.ref === "string" && data.ref.trim()) {
|
|
254
|
-
config.ref = data.ref.trim();
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
errors.push(`${sourceLabel}: projects.${projectId}.ref must be a non-empty string.`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (data.workspaceRoot !== undefined) {
|
|
261
|
-
if (typeof data.workspaceRoot === "string" && data.workspaceRoot.trim()) {
|
|
262
|
-
config.workspaceRoot = resolve(configDir, data.workspaceRoot.trim());
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
errors.push(`${sourceLabel}: projects.${projectId}.workspaceRoot must be a non-empty string.`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (data.workingDirectory !== undefined) {
|
|
269
|
-
if (typeof data.workingDirectory === "string" && data.workingDirectory.trim()) {
|
|
270
|
-
const workingDirectory = data.workingDirectory.trim();
|
|
271
|
-
if (isAbsolute(workingDirectory)) {
|
|
272
|
-
errors.push(`${sourceLabel}: projects.${projectId}.workingDirectory must be a relative path.`);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
config.workingDirectory = workingDirectory;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
errors.push(`${sourceLabel}: projects.${projectId}.workingDirectory must be a non-empty string.`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (data.description !== undefined) {
|
|
283
|
-
if (typeof data.description === "string" && data.description.trim()) {
|
|
284
|
-
config.description = data.description.trim();
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
errors.push(`${sourceLabel}: projects.${projectId}.description must be a non-empty string.`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (data.bootstrapCommands !== undefined) {
|
|
291
|
-
if (!Array.isArray(data.bootstrapCommands) || data.bootstrapCommands.length === 0) {
|
|
292
|
-
errors.push(`${sourceLabel}: projects.${projectId}.bootstrapCommands must be a non-empty string array.`);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
const commands = [];
|
|
296
|
-
for (const command of data.bootstrapCommands) {
|
|
297
|
-
if (typeof command !== "string" || !command.trim()) {
|
|
298
|
-
errors.push(`${sourceLabel}: projects.${projectId}.bootstrapCommands must be a non-empty string array.`);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
commands.push(command);
|
|
302
|
-
}
|
|
303
|
-
if (commands.length > 0) {
|
|
304
|
-
config.bootstrapCommands = commands;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (data.backgroundBootstrapCommands !== undefined) {
|
|
309
|
-
if (!Array.isArray(data.backgroundBootstrapCommands) ||
|
|
310
|
-
data.backgroundBootstrapCommands.length === 0) {
|
|
311
|
-
errors.push(`${sourceLabel}: projects.${projectId}.backgroundBootstrapCommands must be a non-empty string array.`);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
const commands = [];
|
|
315
|
-
for (const command of data.backgroundBootstrapCommands) {
|
|
316
|
-
if (typeof command !== "string" || !command.trim()) {
|
|
317
|
-
errors.push(`${sourceLabel}: projects.${projectId}.backgroundBootstrapCommands must be a non-empty string array.`);
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
commands.push(command);
|
|
321
|
-
}
|
|
322
|
-
if (commands.length > 0) {
|
|
323
|
-
config.backgroundBootstrapCommands = commands;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (data.persona !== undefined) {
|
|
328
|
-
if (typeof data.persona === "string" && data.persona.trim()) {
|
|
329
|
-
config.persona = data.persona.trim();
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
195
|
+
if (config.persona !== undefined) {
|
|
196
|
+
const parsedPersona = parsePersonaReference(config.persona);
|
|
197
|
+
if (parsedPersona.error === "empty-persona") {
|
|
332
198
|
errors.push(`${sourceLabel}: projects.${projectId}.persona must be a non-empty string.`);
|
|
333
199
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const riskLevel = parseRiskLevel(data.riskLevel);
|
|
337
|
-
if (riskLevel) {
|
|
338
|
-
config.riskLevel = riskLevel;
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
errors.push(`${sourceLabel}: projects.${projectId}.riskLevel must be read-only or read-write.`);
|
|
200
|
+
else if (parsedPersona.error === "missing-reasoning") {
|
|
201
|
+
errors.push(`${sourceLabel}: projects.${projectId}.persona is missing a reasoning level after ':'.`);
|
|
342
202
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (typeof data.sandbox === "boolean") {
|
|
346
|
-
config.sandbox = data.sandbox;
|
|
203
|
+
else if (parsedPersona.error === "invalid-reasoning") {
|
|
204
|
+
errors.push(`${sourceLabel}: projects.${projectId}.persona has invalid reasoning level '${parsedPersona.rawReasoning}'. allowed levels: ${REASONING_LEVELS.join(", ")}.`);
|
|
347
205
|
}
|
|
348
|
-
else {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (typeof data.noAgentContextFiles === "boolean") {
|
|
354
|
-
config.noAgentContextFiles = data.noAgentContextFiles;
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
errors.push(`${sourceLabel}: projects.${projectId}.noAgentContextFiles must be a boolean.`);
|
|
206
|
+
else if (parsedPersona.personaId) {
|
|
207
|
+
config.persona = formatPersonaReference({
|
|
208
|
+
personaId: parsedPersona.personaId,
|
|
209
|
+
reasoning: parsedPersona.reasoning,
|
|
210
|
+
});
|
|
358
211
|
}
|
|
359
212
|
}
|
|
360
213
|
if (errors.length > 0) {
|
|
@@ -363,12 +216,13 @@ function parseProject(raw, sourceLabel, projectId, configDir) {
|
|
|
363
216
|
return { config, errors: [] };
|
|
364
217
|
}
|
|
365
218
|
function parseProjects(raw, sourceLabel, configDir) {
|
|
366
|
-
|
|
219
|
+
const parsedObject = z.record(z.string(), z.unknown()).safeParse(raw);
|
|
220
|
+
if (!parsedObject.success) {
|
|
367
221
|
return { projects: {}, errors: [`${sourceLabel}: projects must be an object.`] };
|
|
368
222
|
}
|
|
369
223
|
const errors = [];
|
|
370
224
|
const projects = {};
|
|
371
|
-
for (const [projectId, value] of Object.entries(
|
|
225
|
+
for (const [projectId, value] of Object.entries(parsedObject.data)) {
|
|
372
226
|
if (!projectId.trim()) {
|
|
373
227
|
errors.push(`${sourceLabel}: projects keys must be non-empty.`);
|
|
374
228
|
continue;
|
|
@@ -393,9 +247,19 @@ function parseCronJobMarkdownFile(filePath, projects, sourceLabel) {
|
|
|
393
247
|
],
|
|
394
248
|
};
|
|
395
249
|
}
|
|
396
|
-
const
|
|
250
|
+
const markdownResult = parseMarkdownFrontMatter(content);
|
|
251
|
+
if (!markdownResult.ok) {
|
|
252
|
+
return { errors: [`${sourceLabel}: ${filePath}: ${markdownResult.message}.`] };
|
|
253
|
+
}
|
|
254
|
+
const { frontMatter, body } = markdownResult;
|
|
397
255
|
const fileId = basename(filePath, ".md").trim();
|
|
398
256
|
const errors = [];
|
|
257
|
+
const unknownKeys = Object.keys(frontMatter)
|
|
258
|
+
.filter((key) => !CRON_JOB_FRONTMATTER_KEYS.has(key))
|
|
259
|
+
.sort();
|
|
260
|
+
if (unknownKeys.length > 0) {
|
|
261
|
+
errors.push(formatUnknownKeysError(sourceLabel, `${filePath} frontmatter`, unknownKeys));
|
|
262
|
+
}
|
|
399
263
|
const enabledRaw = frontMatter.enabled;
|
|
400
264
|
if (enabledRaw !== undefined && typeof enabledRaw !== "boolean") {
|
|
401
265
|
errors.push(`${sourceLabel}: ${filePath}: frontmatter enabled must be a boolean when set.`);
|
|
@@ -519,65 +383,23 @@ export function loadAsyncDaemonConfig(configFilePath) {
|
|
|
519
383
|
catch (error) {
|
|
520
384
|
throw new AsyncDaemonConfigError(`${sourceLabel}: failed to read/parse json: ${error instanceof Error ? error.message : String(error)}`);
|
|
521
385
|
}
|
|
522
|
-
|
|
386
|
+
const parsedConfigObject = z.record(z.string(), z.unknown()).safeParse(parsed);
|
|
387
|
+
if (!parsedConfigObject.success) {
|
|
523
388
|
throw new AsyncDaemonConfigError(`${sourceLabel}: config must be an object.`);
|
|
524
389
|
}
|
|
525
|
-
const data =
|
|
390
|
+
const data = parsedConfigObject.data;
|
|
526
391
|
const errors = [];
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (isPositiveInteger(data.port) && data.port <= 65535) {
|
|
539
|
-
port = data.port;
|
|
540
|
-
}
|
|
541
|
-
else {
|
|
542
|
-
errors.push(`${sourceLabel}: port must be a positive integer <= 65535.`);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
let authToken;
|
|
546
|
-
if (data.authToken !== undefined) {
|
|
547
|
-
if (typeof data.authToken === "string" && data.authToken.trim()) {
|
|
548
|
-
authToken = data.authToken.trim();
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
errors.push(`${sourceLabel}: authToken must be a non-empty string.`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
let maxSessions;
|
|
555
|
-
if (data.maxSessions !== undefined) {
|
|
556
|
-
if (isPositiveInteger(data.maxSessions)) {
|
|
557
|
-
maxSessions = data.maxSessions;
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
errors.push(`${sourceLabel}: maxSessions must be a positive integer.`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
let workspaceRoot = resolve(configDir, ".tau", "async-workspaces");
|
|
564
|
-
if (data.workspaceRoot !== undefined) {
|
|
565
|
-
if (typeof data.workspaceRoot === "string" && data.workspaceRoot.trim()) {
|
|
566
|
-
workspaceRoot = resolve(configDir, data.workspaceRoot.trim());
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
errors.push(`${sourceLabel}: workspaceRoot must be a non-empty string.`);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
let systemMessage;
|
|
573
|
-
if (data.systemMessage !== undefined) {
|
|
574
|
-
if (typeof data.systemMessage === "string" && data.systemMessage.trim()) {
|
|
575
|
-
systemMessage = data.systemMessage.trim();
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
errors.push(`${sourceLabel}: systemMessage must be a non-empty string.`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
392
|
+
const topLevelResult = asyncDaemonTopLevelSchema.safeParse(data);
|
|
393
|
+
if (!topLevelResult.success) {
|
|
394
|
+
errors.push(...formatSectionZodErrors(topLevelResult.error, sourceLabel, "config"));
|
|
395
|
+
}
|
|
396
|
+
const topLevel = topLevelResult.success ? topLevelResult.data : {};
|
|
397
|
+
const host = topLevel.host ?? "127.0.0.1";
|
|
398
|
+
const port = topLevel.port ?? 7788;
|
|
399
|
+
const authToken = topLevel.authToken;
|
|
400
|
+
const maxSessions = topLevel.maxSessions;
|
|
401
|
+
const workspaceRoot = resolve(configDir, topLevel.workspaceRoot ?? ".tau/async-workspaces");
|
|
402
|
+
const systemMessage = topLevel.systemMessage;
|
|
581
403
|
const projectsResult = parseProjects(data.projects, sourceLabel, configDir);
|
|
582
404
|
const telegramResult = parseTelegramConfig(data.telegram, sourceLabel, new Set(Object.keys(projectsResult.projects)));
|
|
583
405
|
const cronResult = parseCronConfig(data.cron, sourceLabel);
|