源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Agent tool for the unified ScheduledTask catalog.
|
||||
*
|
||||
* Agent-facing access to the Runtime Host-owned ScheduledTask catalog.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
SCHEDULED_TASK_CRON_MAX_CHARS,
|
||||
SCHEDULED_TASK_INTENT_MAX_CHARS,
|
||||
SCHEDULED_TASK_MAX_INTERVAL_SECONDS,
|
||||
SCHEDULED_TASK_TITLE_MAX_CHARS,
|
||||
type ScheduledTask,
|
||||
type ScheduledTaskExecutionTemplate,
|
||||
} from '@maka/core/scheduled-task';
|
||||
import type { MakaTool } from './tool-runtime.js';
|
||||
|
||||
export const SCHEDULED_TASK_TOOL_NAME = 'ScheduledTask';
|
||||
export const SCHEDULED_TASK_NATIVE_EFFECT_SERVICE_ID = 'maka_scheduled_task_native_effect';
|
||||
export const SCHEDULED_TASK_NATIVE_EFFECT_SERVICE_VERSION = '1';
|
||||
|
||||
export interface ScheduledTaskToolAuthority {
|
||||
create(input: {
|
||||
title: string;
|
||||
intentBody: string;
|
||||
schedule:
|
||||
| { kind: 'once'; runAt: number }
|
||||
| { kind: 'interval'; everySeconds: number; startAt?: number }
|
||||
| { kind: 'cron'; expression: string; startAt?: number };
|
||||
effect: 'session_resume' | 'agent_run' | 'notify_local';
|
||||
sessionId: string;
|
||||
maxFires?: number;
|
||||
}): Promise<ScheduledTask | { error: string }>;
|
||||
list(): Promise<readonly ScheduledTask[]>;
|
||||
pause(id: string): Promise<ScheduledTask | { error: string }>;
|
||||
resume(id: string): Promise<ScheduledTask | { error: string }>;
|
||||
remove(id: string): Promise<{ ok: true } | { error: string }>;
|
||||
}
|
||||
|
||||
const scheduleSchema = z.union([
|
||||
z.object({
|
||||
kind: z.literal('once'),
|
||||
runAt: z.number().int().positive(),
|
||||
}),
|
||||
z.object({
|
||||
kind: z.literal('interval'),
|
||||
everySeconds: z.number().int().min(10).max(SCHEDULED_TASK_MAX_INTERVAL_SECONDS),
|
||||
startAt: z.number().int().positive().optional(),
|
||||
}),
|
||||
z.object({
|
||||
kind: z.literal('cron'),
|
||||
expression: z.string().min(9).max(SCHEDULED_TASK_CRON_MAX_CHARS),
|
||||
startAt: z.number().int().positive().optional(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const schema = z.object({
|
||||
mode: z
|
||||
.enum(['create', 'list', 'pause', 'resume', 'delete'])
|
||||
.describe('create / list / pause / resume / delete a scheduled task'),
|
||||
title: z.string().trim().min(1).max(SCHEDULED_TASK_TITLE_MAX_CHARS).optional(),
|
||||
intentBody: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(SCHEDULED_TASK_INTENT_MAX_CHARS)
|
||||
.optional()
|
||||
.describe('[create] Work description or agent prompt to run when due'),
|
||||
schedule: scheduleSchema.optional(),
|
||||
effect: z
|
||||
.enum(['session_resume', 'agent_run', 'notify_local'])
|
||||
.optional()
|
||||
.describe(
|
||||
'[create] session_resume (default) resumes this conversation; agent_run starts a fresh session; notify_local only shows a local notification',
|
||||
),
|
||||
id: z.string().min(1).optional().describe('[pause|resume|delete] task id'),
|
||||
maxFires: z.number().int().min(1).max(10_000).optional(),
|
||||
});
|
||||
|
||||
export function buildScheduledTaskTool(deps: { authority: ScheduledTaskToolAuthority }): MakaTool {
|
||||
return {
|
||||
name: SCHEDULED_TASK_TOOL_NAME,
|
||||
displayName: 'ScheduledTask',
|
||||
description:
|
||||
'Create and manage global scheduled tasks (定时任务). ' +
|
||||
'Use for every recurring or one-shot task. All tasks appear in the desktop Scheduled tasks page. ' +
|
||||
'The default session_resume effect continues this conversation; use agent_run for independent work.',
|
||||
parameters: schema,
|
||||
impl: async (raw, ctx) => {
|
||||
const input = schema.parse(raw);
|
||||
const sessionId = ctx.sessionId;
|
||||
if (input.mode === 'list') {
|
||||
const tasks = await deps.authority.list();
|
||||
if (tasks.length === 0) return 'No scheduled tasks.';
|
||||
return tasks
|
||||
.map(
|
||||
(task) =>
|
||||
`- ${task.id} | ${task.title} | ${task.status} | next=${task.nextFireAt ?? '—'} | effect=${task.effect.kind}`,
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
if (input.mode === 'create') {
|
||||
if (!input.title || !input.intentBody || !input.schedule) {
|
||||
return 'create requires title, intentBody, and schedule';
|
||||
}
|
||||
const result = await deps.authority.create({
|
||||
title: input.title,
|
||||
intentBody: input.intentBody,
|
||||
schedule: input.schedule,
|
||||
effect: input.effect ?? 'session_resume',
|
||||
sessionId,
|
||||
...(input.maxFires !== undefined ? { maxFires: input.maxFires } : {}),
|
||||
});
|
||||
if ('error' in result) return result.error;
|
||||
return `Scheduled task created: ${result.title} (${result.id})\nnextFireAt=${result.nextFireAt}\neffect=${result.effect.kind}`;
|
||||
}
|
||||
if (!input.id) return `${input.mode} requires id`;
|
||||
if (input.mode === 'pause') {
|
||||
const result = await deps.authority.pause(input.id);
|
||||
if ('error' in result) return result.error;
|
||||
return `Paused ${result.title} (${result.id})`;
|
||||
}
|
||||
if (input.mode === 'resume') {
|
||||
const result = await deps.authority.resume(input.id);
|
||||
if ('error' in result) return result.error;
|
||||
return `Resumed ${result.title} (${result.id})`;
|
||||
}
|
||||
const result = await deps.authority.remove(input.id);
|
||||
if ('error' in result) return result.error;
|
||||
return `Deleted scheduled task ${input.id}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Build create payload for the SQLite store from tool + session execution snapshot. */
|
||||
export function buildAgentScheduledTaskCreatePayload(input: {
|
||||
title: string;
|
||||
intentBody: string;
|
||||
schedule:
|
||||
| { kind: 'once'; runAt: number }
|
||||
| { kind: 'interval'; everySeconds: number; startAt?: number }
|
||||
| { kind: 'cron'; expression: string; startAt?: number };
|
||||
effect: 'session_resume' | 'agent_run' | 'notify_local';
|
||||
sessionId: string;
|
||||
execution?: ScheduledTaskExecutionTemplate;
|
||||
maxFires?: number;
|
||||
now?: number;
|
||||
}):
|
||||
| {
|
||||
title: string;
|
||||
intentBody: string;
|
||||
schedule: ScheduledTask['schedule'];
|
||||
effect: ScheduledTask['effect'];
|
||||
createdBy: ScheduledTask['createdBy'];
|
||||
maxFires?: number;
|
||||
}
|
||||
| { error: string } {
|
||||
const now = input.now ?? Date.now();
|
||||
if (input.effect === 'agent_run' && !input.execution) {
|
||||
return { error: 'agent_run requires a frozen execution template from the creator session' };
|
||||
}
|
||||
const schedule =
|
||||
input.schedule.kind === 'once'
|
||||
? input.schedule
|
||||
: input.schedule.kind === 'interval'
|
||||
? {
|
||||
kind: 'interval' as const,
|
||||
everySeconds: input.schedule.everySeconds,
|
||||
startAt: input.schedule.startAt ?? now,
|
||||
}
|
||||
: {
|
||||
kind: 'cron' as const,
|
||||
expression: input.schedule.expression,
|
||||
startAt: input.schedule.startAt ?? now,
|
||||
};
|
||||
if (input.effect === 'agent_run') {
|
||||
return {
|
||||
title: input.title,
|
||||
intentBody: input.intentBody,
|
||||
schedule,
|
||||
effect: { kind: 'agent_run', execution: input.execution! },
|
||||
createdBy: { kind: 'agent', sessionId: input.sessionId },
|
||||
...(input.maxFires !== undefined ? { maxFires: input.maxFires } : {}),
|
||||
};
|
||||
}
|
||||
if (input.effect === 'session_resume') {
|
||||
return {
|
||||
title: input.title,
|
||||
intentBody: input.intentBody,
|
||||
schedule,
|
||||
effect: { kind: 'session_resume', sessionId: input.sessionId },
|
||||
createdBy: { kind: 'agent', sessionId: input.sessionId },
|
||||
...(input.maxFires !== undefined ? { maxFires: input.maxFires } : {}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: input.title,
|
||||
intentBody: input.intentBody,
|
||||
schedule,
|
||||
effect: { kind: 'notify', channel: 'local' },
|
||||
createdBy: { kind: 'agent', sessionId: input.sessionId },
|
||||
...(input.maxFires !== undefined ? { maxFires: input.maxFires } : {}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user