Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions emrg/gui/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -968,15 +968,15 @@ vision = false

// ── Task/template CRUD IPC (rant 2026-08-12T18:23:15 P3) ──────────
ipcMain.handle("emrg:taskCreate", async (_e, payload) => {
const { name, type, project, interval, enabled, repo, description, sandbox, schedule } = payload || {};
const { name, type, agent, interval, enabled, repo, description, sandbox, schedule } = payload || {};
if (typeof name !== "string" || !name.trim()) throw new Error("invalid task name");
if (typeof type !== "string" || !type.trim()) throw new Error("invalid task type");
if (typeof project !== "string" || !project.trim()) throw new Error("invalid project");
if (typeof agent !== "string" || !agent.trim()) throw new Error("invalid agent");
// `schedule` 必须原样转发:这张固定字段表此前把它丢掉,于是 GUI 永远只能建
// 单一频次的任务,daemon 收了也白收(issue #2490)。只有 payload 真的带了这个
// 键才发,缺席=不声明,与 task_update 的「缺省不动该字段」一致。
const fields = {
name: name.trim(), task_type: type.trim(), project: project.trim(),
name: name.trim(), task_type: type.trim(), agent: agent.trim(),
interval, enabled, repo, description, sandbox,
};
if ("schedule" in (payload || {})) fields.schedule = schedule;
Expand Down
6 changes: 3 additions & 3 deletions emrg/gui/renderer/src/components/TaskFormDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function taskWith(overrides: Partial<TaskRec> = {}): TaskRec {
type: "evolution",
interval: 10800,
enabled: true,
config: { project: "emrg" },
config: { agent: "emrg" },
...overrides,
};
}
Expand Down Expand Up @@ -91,7 +91,7 @@ describe("TaskFormDialog", () => {
});

it("编辑:预填任务字段,name 只读不可改名", () => {
const task = taskWith({ type: "journal", interval: 3600, enabled: false, config: { project: "sci" } });
const task = taskWith({ type: "journal", interval: 3600, enabled: false, config: { agent: "sci" } });
(task as TaskRec & { sandbox?: string }).sandbox = "read-only";
render(
wrapper(
Expand Down Expand Up @@ -161,7 +161,7 @@ describe("TaskFormDialog", () => {
expect(payload).toEqual({
name: "t1",
type: "journal",
project: "sci",
agent: "sci",
interval: 300,
enabled: false,
repo: undefined,
Expand Down
8 changes: 4 additions & 4 deletions emrg/gui/renderer/src/components/TaskFormDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Dialog } from "./Dialog";
export interface TaskFormPayload {
name: string;
type: string;
project: string;
agent: string;
interval: number;
enabled: boolean;
repo?: string;
Expand Down Expand Up @@ -114,8 +114,8 @@ export function TaskFormDialog({
const curType =
(task?.type && types.includes(task.type) ? task.type : types[0]) || "evolution";
if (typeRef.current) typeRef.current.value = curType;
const curProj = projects.some((p) => (p.name || p.path || "") === task?.config?.project)
? (task?.config?.project as string)
const curProj = projects.some((p) => (p.name || p.path || "") === task?.config?.agent)
? (task?.config?.agent as string)
: (projects[0]?.name || projects[0]?.path || "");
if (projectRef.current) projectRef.current.value = curProj;
if (intervalRef.current) intervalRef.current.value = String(task?.interval ?? 1800);
Expand Down Expand Up @@ -176,7 +176,7 @@ export function TaskFormDialog({
const payload: TaskFormPayload = {
name,
type: typeRef.current.value,
project: projectRef.current.value,
agent: projectRef.current.value,
interval,
enabled: enabledRef.current?.checked ?? true,
repo: repoRef.current?.value.trim() || undefined,
Expand Down
2 changes: 1 addition & 1 deletion emrg/gui/renderer/src/components/WorkspaceView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe("WorkspaceView", () => {
expect(screen.getByTestId("projects-empty")).toBeInTheDocument();

const projects = [project("emrg"), project("aitokenpool", "/scm/aitokenpool")];
const tasks = [{ name: "emrg-task", config: { project: "emrg" } }] as TaskRec[];
const tasks = [{ name: "emrg-task", config: { agent: "emrg" } }] as TaskRec[];
const { rerender } = setup({ projects, tasks });
expect(screen.getAllByTestId("project-row")).toHaveLength(2);
// emrg 有演化任务 → ⚡ 徽标;aitokenpool 无
Expand Down
8 changes: 4 additions & 4 deletions emrg/gui/renderer/src/lib/workspaceView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ describe("preprocessRantMarkdown", () => {
});

describe("buildEvolveProjects", () => {
it("收集 config.project 集合(含 evolution 任务)", () => {
it("收集 config.agent 集合(含 evolution 任务)", () => {
const tasks: TaskRec[] = [
{ name: "a", config: { project: "emrg" } },
{ name: "b", config: { project: "aitokenpool" } },
{ name: "a", config: { agent: "emrg" } },
{ name: "b", config: { agent: "aitokenpool" } },
{ name: "c", config: {} },
];
const set = buildEvolveProjects(tasks);
Expand Down Expand Up @@ -176,7 +176,7 @@ describe("taskStatusBadge", () => {

describe("taskHint", () => {
it("项目 / 间隔 / 停用 join ·", () => {
const h = taskHint({ config: { project: "emrg" }, interval: 1800 }, t);
const h = taskHint({ config: { agent: "emrg" }, interval: 1800 }, t);
expect(h).toBe('emrg · app.taskInterval={"n":1800}');
expect(taskHint({ enabled: false }, t)).toBe("app.taskDisabled");
});
Expand Down
8 changes: 4 additions & 4 deletions emrg/gui/renderer/src/lib/workspaceView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface TaskRec {
interval?: number | null;
last_run_at?: string | null;
saturation?: { heartbeat_active?: boolean; heartbeat_interval?: number } | null;
config?: { project?: string; repo?: string; sandbox?: string } | null;
config?: { agent?: string; repo?: string; sandbox?: string } | null;
/** 分时段频次窗口;缺省=全天单一 `interval`(`list_tasks()` 逐字回报) */
schedule?: ScheduleWindow[] | null;
/** 任务面板「打开会话」所需字段(daemon TaskHandler.status() 返回,vanilla #924 迁移时丢失) */
Expand Down Expand Up @@ -142,11 +142,11 @@ export function preprocessRantMarkdown(text?: string | null): string {

/* ── 项目面板工具 ────────────────────────────────────────── */

/** auto_evolve 徽标集合:tasks.yml 中 config.project 与项目名相同(含 evolution 类型) */
/** auto_evolve 徽标集合:tasks.yml 中 config.agent 与项目名相同(含 evolution 类型) */
export function buildEvolveProjects(tasks: TaskRec[] | null | undefined): Set<string> {
const set = new Set<string>();
for (const t of tasks || []) {
const proj = t?.config?.project;
const proj = t?.config?.agent;
if (proj) set.add(proj);
}
return set;
Expand Down Expand Up @@ -202,7 +202,7 @@ export function taskStatusBadge(t: TaskRec, tFn?: TFunc, now: number = Date.now(
export function taskHint(t: TaskRec, tFn?: TFunc): string {
const tr = tFn || ((k: string) => k);
const parts: string[] = [];
if (t.config?.project) parts.push(t.config.project);
if (t.config?.agent) parts.push(t.config.agent);
if (t.interval != null) parts.push(tr("app.taskInterval", { n: t.interval }));
if (t.enabled === false) parts.push(tr("app.taskDisabled"));
return parts.join(" · ");
Expand Down
8 changes: 8 additions & 0 deletions emrg/server/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from emrg.logfiles import migrate_legacy_logs
from emrg.server.daemon import DaemonExit, _non_regular_kind, run_server
from emrg.server.logcontext import session_label
from emrg.server.scheduler import migrate_task_config_keys
from emrg.tool_path import ensure_tool_dirs


Expand Down Expand Up @@ -202,6 +203,13 @@ def main() -> None:
# fails without touching the host's register at all, and it sits in no path
# derivation — asking where the register lives must never move it.
migrate_legacy_registry()
# The task table's agent key moved from `project` to `agent` (issue #2531),
# and it moves here for the same reason: this is a process that owns the
# file, it runs before the scheduler is constructed below, and no test path
# reaches it — so a suite run cannot rewrite a host's live table under a
# daemon still reading the old key. Asking a record for its agent never
# writes; only this call does.
migrate_task_config_keys()
result = asyncio.run(run_server(config.llm))
except KeyboardInterrupt:
# SIGINT delivered at the event-loop poll point escapes the main
Expand Down
4 changes: 2 additions & 2 deletions emrg/server/competition_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ You are EMRG's competition participation module. **Every cycle you MUST fully ex
5. **Think before acting.** Before an irreversible or quota-consuming action — a submission, a registration, starting a long run — state in the reply **what will be done, on what evidence, the expected result, and the cost of failure**. If the evidence is not there yet, go and get it rather than doing it to find out.

### Current State
- Task project: **{{ task.project }}** (from tasks.yml)
- Task agent: **{{ task.agent }}** (from tasks.yml)
- Local source: `{{ source_dir }}`
- **Current time: `{{ timestamp }}`({{ current_time_human }})** — the time anchor for judging deadlines

Expand Down Expand Up @@ -58,7 +58,7 @@ Cross-round continuity comes from what the daemon already gives you: it loads th

Rants are how the host sends instructions directly to a task.

- A rant matches **only if its `project` field is exactly `{{ task.project }}`**. Rants that do not match are ignored completely.
- A rant matches **only if its `project` field is exactly `{{ task.agent }}`**. Rants that do not match are ignored completely.
- Unmatched but seemingly relevant rants must be **counted and reported to the host** — never silently dropped.
- A matching rant is an instruction for this round: it takes priority over the round's planned work.

Expand Down
6 changes: 3 additions & 3 deletions emrg/server/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2937,7 +2937,7 @@ async def _process_message(
if self._scheduler:
ok, res = self._scheduler.task_create(
name=name, task_type="evolution",
project=name, interval=600,
agent=name, interval=600,
)
if ok:
self._scheduler.reconcile()
Expand Down Expand Up @@ -3004,7 +3004,7 @@ async def _process_message(
ok, res = self._scheduler.task_create(
name=msg.get("name", "").strip(),
task_type=msg.get("task_type", "").strip(),
project=msg.get("project", "").strip(),
agent=msg.get("agent", "").strip(),
interval=msg.get("interval"),
enabled=msg.get("enabled", True),
repo=msg.get("repo"),
Expand All @@ -3026,7 +3026,7 @@ async def _process_message(
if not self._scheduler:
await self._send(ws, {"type": "task_result", "error": "scheduler not running"})
return
fields = {k: msg[k] for k in ("task_type", "project", "interval", "enabled", "repo", "description", "sandbox", "schedule") if k in msg}
fields = {k: msg[k] for k in ("task_type", "agent", "interval", "enabled", "repo", "description", "sandbox", "schedule") if k in msg}
if "task_type" in fields:
fields["type"] = fields.pop("task_type")
ok, res = self._scheduler.task_update(msg.get("name", "").strip(), **fields)
Expand Down
2 changes: 1 addition & 1 deletion emrg/server/evolution_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Two ways this source goes quiet, and both are legitimate: an installation whose
- **Issue**: something needing a reply or triage, or a resolved one to close — the close itself is §3.1. What finishes an issue, and how the link reading is read, is R5.
- **Rant**: take each one through three questions.
- **Has it been handled?** `git log --oneline -20` for a commit naming its timestamp or keywords. Naming proves only that it was **touched**, never that it is finished — check for unmet acceptance items, unmerged branches, and a stage that landed without finishing the work.
- **Does it match this task?** A rant's `project` matches if it is `{{ task.project }}` or `{{ owner }}/{{ repo }}`; a rant that names neither is not this instance's to act on — **ignore rants without a `project` field entirely**.
- **Does it match this task?** A rant's `project` matches if it is `{{ task.agent }}` or `{{ owner }}/{{ repo }}`; a rant that names neither is not this instance's to act on — **ignore rants without a `project` field entirely**.
- **Is it work?** A rant this cycle decides against is closed with the reason in its progress; a rant taken on becomes an issue before any code is written (R5). The lifecycle is R6. **Before any reason is written down as the host's ruling, R7.**

#### 2.3 Decide this cycle's work
Expand Down
4 changes: 2 additions & 2 deletions emrg/server/feishu_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ You are EMRG handling the host's Feishu messages: read what arrived, judge each
what needs no permission, ask where it does.

### Current State
- Task project: **{{ task.project }}** (from tasks.yml)
- Task agent: **{{ task.agent }}** (from tasks.yml)
- Local source: `{{ source_dir }}`
- **Current time: `{{ timestamp }}`({{ current_time_human }})** — the anchor for "since the last round" and for every dated record

Expand Down Expand Up @@ -47,7 +47,7 @@ in the usual install locations before calling it blocked.
task instructions.
- Take the messages that arrived after the last one you handled. Remember that boundary
yourself; there is no state file to keep and none to trust.
- Rants whose `project` is `{{ task.project }}` are the host's instructions for this round
- Rants whose `project` is `{{ task.agent }}` are the host's instructions for this round
and outrank the plan; those that do not match are counted, not acted on.
- A CLI that cannot run is a `blocked` item reported to the host, and so is an account that
really cannot authenticate — but `missing identity` is a symptom, not a cause. The CLI
Expand Down
4 changes: 2 additions & 2 deletions emrg/server/ios_app_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You are EMRG as the **product owner** of iOS applications: you choose what to bu
**Every round runs §0 Preparation first (§0.1–§0.7), whichever phase it then enters.**

### Current State
- Task project: **{{ task.project }}** (from tasks.yml)
- Task agent: **{{ task.agent }}** (from tasks.yml)
- Local source: `{{ source_dir }}`
- **Current time: `{{ timestamp }}`({{ current_time_human }})** — the time anchor for judging review turnaround and every dated record

Expand Down Expand Up @@ -60,7 +60,7 @@ Everything needed to build, sign and upload an iOS app is installed. **Adding a

Rants are how the host sends instructions directly to a task.

- A rant matches **only if its `project` field is exactly `{{ task.project }}`**. Rants that do not match are ignored completely.
- A rant matches **only if its `project` field is exactly `{{ task.agent }}`**. Rants that do not match are ignored completely.
- Unmatched but seemingly relevant rants must be **counted and reported to the host**, never silently dropped.
- A matching rant is an instruction for this round and takes priority over the round's planned work.

Expand Down
8 changes: 4 additions & 4 deletions emrg/server/journal_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ cat ~/.emrg/rants.jsonl 2>/dev/null || echo "[no rants.jsonl — skip rant scan]
```

Filter rules (same as open-source tasks):
- Match rant's `project` against exactly `{{ task.project }}` — equal counts, anything else does not
- Match rant's `project` against exactly `{{ task.agent }}` — equal counts, anything else does not
- Ignore rants without a `project` field; only `pending` / `in_progress` count
- **Irrelevant-rant exclusion (HARD)**: any rant that does NOT match `project` exactly, or whose content is not about THIS journal, must be **ignored entirely** — do NOT read it into your thinking, do NOT adopt it as a research direction, do NOT cite it. **An irrelevant rant must never become a topic anchor.** Only a rant with `project` = `{{ task.project }}` AND content actually directed at this journal enters the candidate pool / Phase Ops.
- **Irrelevant-rant exclusion (HARD)**: any rant that does NOT match `project` exactly, or whose content is not about THIS journal, must be **ignored entirely** — do NOT read it into your thinking, do NOT adopt it as a research direction, do NOT cite it. **An irrelevant rant must never become a topic anchor.** Only a rant with `project` = `{{ task.agent }}` AND content actually directed at this journal enters the candidate pool / Phase Ops.
- **Dedup check** before treating any rant as actionable: search the journal repo commit log for the rant's timestamp/keywords — a commit referencing the rant only counts as handled when ALL acceptance items are satisfied AND related PRs merged
- **Rant status machine**: `pending → in_progress → completed` (never jump pending → completed). When starting work on a rant: set `in_progress` + progress note. When all its PRs merged + self-verification passes: set `completed` + ISO timestamp. Host feedback that a fix is insufficient → revert to `in_progress` with reason.
- Rant-driven journal ops (e.g. "adjust CfP", "revise review policy") are processed in Phase Ops with highest priority.
Expand Down Expand Up @@ -370,7 +370,7 @@ Nothing pending? → Phase Research (new di
- Read the 2–3 most relevant recent abstracts (last 6 months), record their limitations → this forms your gap. If browser-harness is available, prefer it for reading full texts.
- **The editor's CfP / journal themes**: README, CfP issue, editorial comments — topics the journal explicitly wants.
- **Your own papers' future work**: Discussion / Future-Work paragraphs of your published or in-review manuscripts. **Own-work relevance is NOT by itself a topic reason** — it is only one candidate among many.
- **Host rants with `project` = {{ task.project }}**: directional feedback, not just bug reports. (Per §0.5, irrelevant rants are excluded entirely.)
- **Host rants with `project` = {{ task.agent }}**: directional feedback, not just bug reports. (Per §0.5, irrelevant rants are excluded entirely.)
- **Uncovered gaps in the journal**: open issues / published papers that raise questions nobody has answered yet.
- **Data-source debias**: having data for a particular project on hand is NOT a topic reason. Prefer directions backed by external data sources (public datasets, open-source repositories, simulations) or where data can be obtained/created during the research.
2. **Research-hotspot identification** (using the current-time anchor; run in parallel with the candidate pool):
Expand Down Expand Up @@ -544,7 +544,7 @@ cd {{ source_dir }} && gh issue list -R {{ owner }}/{{ repo }} --label in-review

1. **Never touch host dirt**: never stash/reset/clean host work — the daemon converges the tree itself before the prompt, so a tree still dirty here is a convergence that failed (and the round is then read-only); `papers/*/research/` being present is normal
2. **git add exact path**: submission/revision commits use `git add papers/issue-<N>/` — never `-A` / `.`
3. **Rant handling**: follow 0.5 — project must equal `{{ task.project }}`; pending → in_progress → completed
3. **Rant handling**: follow 0.5 — project must equal `{{ task.agent }}`; pending → in_progress → completed
4. **Language policy**: external journal-facing text (issues/PRs/reviews/decisions) in English; internal records (session history, memory entries, the closing summary) in the author's language
5. **One thing at a time**: advance exactly one phase per cycle; don't aim for completeness, just for progress
6. **Never claim/review your own submission** (author)
Expand Down
Loading
Loading