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