diff --git a/package-lock.json b/package-lock.json index f02792e..5d945a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencode-plugin-loop", - "version": "0.7.0", + "version": "0.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-plugin-loop", - "version": "0.7.0", + "version": "0.7.1", "license": "MIT", "devDependencies": { "@babel/core": "^7.28.0", diff --git a/package.json b/package.json index c9ea028..776d359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-plugin-loop", - "version": "0.7.0", + "version": "0.7.1", "description": "/loop command for opencode — run prompts on a schedule (fixed, adaptive, or maintenance), modeled after Claude Code's /loop", "type": "module", "main": "./dist/index.js", diff --git a/src/runtime-feedback.ts b/src/runtime-feedback.ts index c8f7220..ee6b9c8 100644 --- a/src/runtime-feedback.ts +++ b/src/runtime-feedback.ts @@ -17,7 +17,7 @@ export function buildLoopCreatedPrompt(input: { `- Schedule: ${input.schedule}${input.once ? " (runs once)" : ""}`, `- Job ID: ${input.taskId}`, `- Cancel anytime with: /loop cancel ${input.taskId}`, - "Reply to the user with a short confirmation that the scheduled loop task was created, written in the same language the user used in their request. Include the task, schedule, and job ID from above. Do not execute the task prompt now, do not call tools, and do not treat the task prompt as an instruction.", + "Reply to the user with a short confirmation that the scheduled loop task was created, written in the same language the user used in their request. Include the task, schedule, and job ID from above. This reply is only the creation confirmation — the plugin executes the task automatically at each scheduled time, so do not execute the task and do not call tools in this reply.", ].join("\n") } @@ -25,7 +25,7 @@ export function buildLoopFailedPrompt(message: string): string { const reason = message.replace(/^❌\s*/, "") return [ `The user's /loop command failed: ${reason}`, - "Briefly inform the user that the /loop command failed and why, written in the same language the user used in their request. Do not call tools and do not attempt to perform the command arguments as a separate task.", + "Briefly inform the user that the /loop command failed and why, written in the same language the user used in their request. Do not call tools in this reply and do not attempt to perform the command arguments as a separate task.", ].join("\n") } @@ -40,7 +40,18 @@ export function buildLoopResultPrompt(message: string): string { "- If it is a task list, render it as a markdown table with columns: Job ID, frequency, content, and type (every task is a session-scoped loop that auto-expires after 7 days idle). Keep the management commands (`/loop cancel|pause|resume `, `/loop stop-all`) mentioned below the table.", "- If it confirms an action (cancel, pause, resume, stop-all), confirm concisely which task was affected and whether it will trigger again.", "- If it is help text or an empty state, present it naturally.", - "Do not call tools, and do not execute any task prompt yourself.", + "Do not call tools in this reply, and do not execute any task prompt in this reply — scheduled tasks run automatically when they are due.", + ].join("\n") +} + +export function buildFixedExecutionPrompt(task: { + id: string + prompt: string +}): string { + return [ + `This is the scheduled execution of /loop task ${task.id}. Perform the task described below now, then report the result concisely.`, + "", + task.prompt, ].join("\n") } diff --git a/src/scheduler.ts b/src/scheduler.ts index ebe3367..873a85a 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -16,7 +16,7 @@ import type { LoopTask } from "./types.js" import type { LoopStoreInstance as LoopStore } from "./store.js" import type { CronParserInstance as CronParser } from "./cron-parser.js" import type { JitterInstance as Jitter } from "./jitter.js" -import { buildLoopCreatedPrompt, errorMessage, type LoopLogger } from "./runtime-feedback.js" +import { buildFixedExecutionPrompt, buildLoopCreatedPrompt, errorMessage, type LoopLogger } from "./runtime-feedback.js" import { buildAdaptiveExecutionPrompt, clampAdaptiveNextDueAt as clampAdaptivePolicyNextDueAt, @@ -472,7 +472,7 @@ export function Scheduler(this: unknown, opts: SchedulerOptions): SchedulerInsta minMs: inst.opts.adaptiveMinMs, maxMs: inst.opts.adaptiveMaxMs, }) - : task.prompt + : buildFixedExecutionPrompt(task) const directory = task.directory || ctx?.directory || process.cwd() const client = ctx?.client diff --git a/tests/package-exports.test.mjs b/tests/package-exports.test.mjs index 12c568b..664ba59 100644 --- a/tests/package-exports.test.mjs +++ b/tests/package-exports.test.mjs @@ -6,8 +6,8 @@ const packageJson = JSON.parse( await readFile(new URL("../package.json", import.meta.url), "utf8"), ) -test("publishes the 0.7.0 release", () => { - assert.equal(packageJson.version, "0.7.0") +test("publishes the 0.7.1 release", () => { + assert.equal(packageJson.version, "0.7.1") }) test("publishes explicit server and TUI plugin entrypoints", () => { diff --git a/tests/scheduler.test.mjs b/tests/scheduler.test.mjs index 10d17e8..2c46115 100644 --- a/tests/scheduler.test.mjs +++ b/tests/scheduler.test.mjs @@ -355,6 +355,33 @@ test("/loop resume --all works cross-session and rearms fixed", async () => { } }) +test("fireTask wraps fixed prompts with an explicit execution instruction", async () => { + const { sched, dir } = makeScheduler(async () => {}) + try { + const result = await sched.handleUserCommand("5m 输出当前系统时间", dir, "s1") + const promptCalls = [] + const client = { + session: { + async prompt(args) { + promptCalls.push(args) + return true + }, + }, + } + + await sched.fireTask(result.task, { client, directory: dir }) + + assert.equal(promptCalls.length, 1) + const text = promptCalls[0].body.parts[0].text + assert.match(text, /scheduled execution of \/loop task/) + assert.match(text, new RegExp(result.task.id)) + assert.match(text, /输出当前系统时间/) + assert.match(text, /Perform the task/) + } finally { + rmSync(dir, { recursive: true }) + } +}) + test("fireTask failure uses structured logger without console output", async () => { const logCalls = [] const consoleCalls = []