Commit Graph

11 Commits

Author SHA1 Message Date
Bohan Jiang
7f9e4e829d feat(comments): thread-internal --tail pagination + reply cursor (MUL-2421) (#2846)
* feat(comments): thread-internal pagination via --tail + reply cursor (MUL-2421)

Long threads inside a single issue still forced agents to read every reply
once they used --thread, even after MUL-2387 fixed cross-thread noise. This
adds reply-level paging so a 200-reply thread can be navigated tail-first
without dragging the whole conversation into prompt context.

- New SQL query ListThreadCommentsForIssuePaged: same recursive root walk
  as the legacy thread query, but caps reply count and supports an
  (created_at, id) composite cursor. Root is unconditional — even tail=0
  emits it so the reader keeps the "what is this thread about" context.
- Handler ListComments: parses `tail` (non-negative, ThreadTailSet flag
  preserves the tail=0 intent), threads it through to the paged query,
  and re-uses X-Multica-Next-Before / X-Multica-Next-Before-Id for the
  reply cursor. Cursor's meaning is now context-dependent: thread cursor
  under --recent, reply cursor under --thread + --tail.
- CLI: new --tail flag (only valid with --thread; mutually exclusive
  with --recent), reply-cursor semantics for --before / --before-id when
  paired with --thread + --tail, stderr label flips to "Next reply cursor"
  so an operator copy-pasting the cursor knows which scope it scrolls.
- Tests cover the new contract: tail=N keeps newest N + root, tail=0 is
  root-only, anchor on a nested reply still walks up, reply cursor
  scrolls older replies page-by-page, since combined with tail filters
  after the cut, and the negative-flag-combination matrix.

Out of scope: prompt template update to hint at `--thread <id> --tail 30`
on long threads — separate follow-up per the issue.

Co-authored-by: multica-agent <github@multica.ai>

* fix(comments): only emit reply cursor when older reply exists (MUL-2421)

The thread-tail path emitted `X-Multica-Next-Before` whenever the page
filled to exactly the requested reply count, even when there was nothing
older to scroll to. So `--thread <root> --tail 3` on a thread with
exactly 3 replies sent a cursor that, when followed, returned just the
root — a wasted round-trip that surfaced as a phantom "older replies"
affordance in the agent prompt.

Switch to a `reply_limit + 1` probe: ask the SQL for one extra row, trim
the oldest overflow before responding, and only emit the cursor when an
older reply actually existed. The exact-boundary case (replyCount ==
tail with no overflow) now returns no cursor.

Also documents `--thread/--tail/--recent/--before` and the cursor
semantics in CLI_AND_DAEMON.md, which was the second must-fix in the
MUL-2421 review.

Co-authored-by: multica-agent <github@multica.ai>

* fix(comments): suppress reply cursor when --since covers older replies (MUL-2421)

In the thread + tail + since path the server still emitted a reply cursor
whenever there was an older reply on disk, regardless of `since`. If the
oldest retained reply on the page was already `<= since`, every older
reply was guaranteed to be filtered out too, so the next page only ever
returned the root — wasting round-trips until the agent walked the whole
pre-`since` history. Mirror the recent + since suppression: when
`replies[0].CreatedAt <= since`, drop the cursor.

Test covers the exact case from Elon's review: tail=2 overflow, body
keeps a fresher reply, but the cursor target (oldest retained reply) is
already past `since` — header must be empty.

Co-authored-by: multica-agent <github@multica.ai>

* feat(prompt): default comment-trigger reads to --thread --tail 30 (MUL-2421)

Comment-triggered agents previously defaulted the trigger-thread read to
the unbounded `--thread <id> --output json`, which dumps the full thread
into the prompt — exactly the kind of context bloat MUL-2387 fixed at the
cross-thread layer but never bounded inside a single thread.

Use the new `--tail` flag landed earlier in this PR (server + CLI) as the
default for both the per-turn prompt and the runtime-config Workflow:

- `--thread <trigger-id> --tail 30 --output json` is the new default.
  Root is always included so "what is this about" context survives.
- If 30 replies aren't enough, the prompt now spells out the reply
  cursor: re-feed the stderr `Next reply cursor: --before <ts>
  --before-id <reply-id>` pair back to walk older replies.
- `--recent 20` stays as the cross-thread background fallback, with an
  explicit callout that the same `--before` / `--before-id` flags walk
  *threads* (not replies) in that mode.
- Available Commands core line now surfaces `--tail N` and both stderr
  cursor labels so non-workflow callers also discover the flag.
- `--since` callouts reflect the post-MUL-2421 combinable mode names
  (`--thread --tail` / `--recent`).

Tests (`prompt_test.go`, `execenv_test.go`) pin the new defaults and add
a regression guard against the unbounded `--thread` recipe sneaking back
in.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-21 13:43:15 +08:00
Bohan Jiang
e19f7967b9 feat(prompt): thread-first comment reads for agent runs (MUL-2387) (#2816)
* feat(prompt): thread-first comment reads for agent runs (MUL-2387)

PR #2787 added --thread / --recent / --before / --before-id to the
ListComments API and CLI but kept the agent prompt steering at the
legacy "dump everything" recipe. On a long-running issue the flat dump
burns context on chatter unrelated to the trigger; agents acting on the
trigger want the trigger's thread first.

Prompt updates:

- Comment-triggered Workflow (runtime_config.go) now anchors step 2 on
  `multica issue comment list <issue-id> --thread <trigger-comment-id>
  --output json`. Fallback offers `--recent 20 --output json` with the
  stderr `Next thread cursor: --before <ts> --before-id <root-id>` line
  feeding the next-page cursor. `--since` is preserved and explicitly
  marked combinable with --thread / --recent.
- Per-turn buildCommentPrompt (prompt.go) carries the same thread-first
  guidance so a Codex-style runtime that re-reads the per-turn message
  every iteration gets the same steering, even if it ignores the
  injected runtime config.
- Assignment-triggered Workflow keeps the mandatory full-history rule
  (MUL-1124) but now also points at `--recent 20` as the long-issue
  alternative — this is the place that previously had no thread-aware
  guidance at all.
- Default fallback prompt (no trigger comment, no chat, no autopilot,
  no quick-create) gains the same --recent hint without --thread (no
  comment to anchor on).
- Available Commands core line surfaces the new flags so the discovery
  path matches the workflow guidance.

Default CLI/API semantics are unchanged: the unparameterized list still
returns the full chronological dump capped at 2000, --since still works
on its own, and the desktop UI is untouched.

Tests:

- prompt_test.go: TestBuildPromptCommentTriggerPromotesThreadReads pins
  --thread <triggerID>, --recent 20, the stderr cursor phrasing, and
  the absence of the legacy "returns all comments" prose.
- prompt_test.go: TestBuildPromptDefaultMentionsRecent guards the
  no-trigger fallback (mentions --recent, must NOT mention --thread).
- execenv_test.go: TestInjectRuntimeConfigCommentTriggerThreadFirstReads
  asserts the comment-triggered Workflow steers at --thread/--recent,
  the Available Commands line surfaces the new flags, and the legacy
  "read the conversation (returns all comments...)" string is gone.
- execenv_test.go: TestInjectRuntimeConfigAssignmentTriggerMentionsRecent
  keeps the mandatory full-history rule pinned AND asserts --recent is
  offered as the long-issue alternative.

Also fixes the recent+since cursor nit Elon flagged in #2787's second
review: when `since` empties the page, the `len(seenRoot) >= recentN`
check used to emit a cursor anyway. Pagination walks threads in
strictly decreasing last_activity_at — if every comment in this page is
<= since, every older thread's last_activity is also <= since by
transitivity, so the cursor would only invite the caller into a
guaranteed-empty walk. Now suppressed; new tests pin both branches
(suppressed when empty, retained when at least one row passes since).

MUL-2387

Co-authored-by: multica-agent <github@multica.ai>

* fix(comments): suppress recent+since cursor when head thread past since (MUL-2387)

Previous suppression only tripped when the `since` filter emptied the
page. That missed the mixed case Elon flagged in #2787's second review:
the page keeps rows from fresher threads but the head (oldest-active)
thread already sits at or before `since`, so every older page is
guaranteed empty too. Predicating on `headLast <= since` covers both
cases.

Add a recent=2 + since fixture that pins the mixed scenario: root1
(last_activity = base+3m) is filtered out, root2 stays, and the cursor
is suppressed even though the body is non-empty.

Co-authored-by: multica-agent <github@multica.ai>

* fix(prompt): clarify --recent is paging, not a replacement (MUL-2387)

Address Elon's second-pass nit on #2816: the assignment-trigger workflow
in runtime_config.go used "you may switch to --recent 20", which reads as
a replacement for the mandatory full-history rule. Rephrase --recent as a
paging strategy ("read the full history page-by-page, not a shortcut that
replaces it") so it cannot conflict with the rule it lives next to.

The default per-turn prompt in prompt.go opened with "If you need comment
history" — that soft conditional contradicts the runtime workflow's
mandatory read. Move it to a neutral "For comment history, follow the
rule in your runtime workflow file" framing that defers to whatever the
workflow says (mandatory for assignment, optional elsewhere) instead of
encoding its own policy.

Keep the runtime/prompt dual-layer fallback intact — different runtimes
propagate the config file vs. the per-turn user prompt with varying
fidelity, so both surfaces need the guidance.

Tests pin the new phrasing against regression:

- TestBuildPromptDefaultMentionsRecent now also forbids "If you need
  comment history" from sneaking back in.
- TestInjectRuntimeConfigAssignmentTriggerMentionsRecent now also forbids
  "you may switch to" / "switch to `--recent" replacement phrasing.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-19 14:42:35 +08:00
Valentin Mihov
da7b33561e fix: make quick-create output prefix agnostic (#2604)
* fix: make quick-create output prefix agnostic

* fix: remove quick-create prefix assumption from runtime config
2026-05-15 12:20:53 +08:00
Bohan Jiang
fdf19cac8f fix(quick-create): default squad-picked issues to the squad, not the leader (#2611)
When the user opens quick-create with a squad selected, the task is
enqueued against the squad's leader agent — but the squad, not the
leader, is the expected owner. The prompt previously instructed the
leader to "default to YOURSELF" using its own agent UUID, hiding new
issues from the squad's delegation flow.

Surface the squad's id + name on the claim response and branch the
default-assignee instruction in buildQuickCreatePrompt: when SquadID is
present, point --assignee-id at the squad UUID and explicitly forbid
self-assignment.

MUL-2203

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 17:48:02 +08:00
Multica Eve
58cc189dcd fix: honor quick-create squad mentions (#2586)
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 14:01:37 +08:00
LinYushen
376cc8372a fix: inject squad leader no_action rule for member-triggered comments (#2576)
The per-turn prompt in buildCommentPrompt() only injected the squad
leader no_action prohibition inside the 'if TriggerAuthorType == agent'
block. When a member (human) posted a comment like 'LGTM', the squad
leader was triggered but the per-turn prompt did NOT include the
prohibition, causing the model to post noise comments like 'LGTM is a
pure acknowledgment — no reply needed. Exiting silently.'

Fix: move the squad leader no_action rule outside the agent-only block
so it fires for ALL trigger types (agent and member).

Fixes: MUL-2168

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:01:07 +08:00
Bohan Jiang
21b49eb59b fix(cli): resolve squad assignees in issue create/update/assign (MUL-2165) (#2551)
* fix(cli): resolve squad assignees in issue create/update/assign (MUL-2165)

The CLI assignee resolver only searched workspace members and agents, so a
quick-create input like "assign to <SquadName>" silently fell through to
"Unrecognized assignee: <SquadName>" in the issue description — even though
squads are first-class assignees server-side and the prompt's whole point was
to route the work for the user.

Extend resolveAssignee / resolveAssigneeByID to also fetch /api/squads, teach
the actor display lookup to render squad names in table output, update the
quick-create prompt and runtime-config command listing to mention
`multica squad list` alongside members and agents, and lock in the new
behavior with tests.

Co-authored-by: multica-agent <github@multica.ai>

* fix(cli): gate squad assignee resolution behind an allowed-kinds set (MUL-2165)

The earlier MUL-2165 fix taught resolveAssignee / resolveAssigneeByID to also
return (squad, ...), but those helpers are shared. Project lead and issue
subscriber callers were still using them, and their target schemas reject
squads — project.lead_type has a DB CHECK constraint
(server/migrations/034_projects.up.sql:10) and the subscriber handler's
isWorkspaceEntity switch only knows member/agent
(server/internal/handler/handler.go:414). So
`multica project create --lead "<SquadName>"` and
`multica issue subscriber add --user "<SquadName>"` would resolve to
(squad, ...) and surface as a 500/403 server-side instead of a clean
CLI-side resolution error.

Thread an assigneeKinds set through the resolver and the pickAssigneeFromFlags
helper. Issue create/update/assign/list pass `issueAssigneeKinds` (all three);
project lead and subscriber pass `memberOrAgentKinds`. The squads fetch is
skipped entirely when not allowed, and the not-found / no-match error wording
adapts to the allowed kinds so it never mentions a type the caller cannot use.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 22:31:50 +08:00
Bohan Jiang
003dfd9b4b feat(quick-create): add project picker that remembers last pick (#2321)
* feat(quick-create): add project picker that remembers last pick

Quick-create users targeting one project repeatedly had to restate "in
project X" in every prompt. The modal now exposes a project picker beside
the agent picker, persists the selection per-workspace, and pins the
agent's `multica issue create` invocation to that project so the prompt
text doesn't have to.

The picked project also flows to the daemon as ProjectID/ProjectTitle and
its github_repo resources override the workspace repo fallback — same
treatment issue-bound tasks already get.

Co-authored-by: multica-agent <github@multica.ai>

* fix(quick-create): move project picker into property pill row

Reviewer feedback: the picker felt out of place wedged next to the agent
header. Move it into a property toolbar row above the footer, reusing the
shared `ProjectPicker` + `PillButton` so its placement and styling line up
exactly with the manual create panel.

This also drops the bespoke dropdown / aria / label strings that were only
needed while the picker rendered inline beside "Created by".

Co-authored-by: multica-agent <github@multica.ai>

* fix(quick-create): clear stale persisted project + carry across mode switch

Two review-blocking bugs in PR #2321:

1. The stale-id sweep in AgentCreatePanel only fired when projects.length > 0
   and only cleared local state, leaving lastProjectId pointing at a deleted
   project. The next open re-seeded the dead UUID and submit hit the server's
   `project not found` rejection. Gate on the query's `isSuccess` so we can
   tell "loading" apart from "loaded as empty", and clear both local state
   and the persisted preference when the selection isn't in the resolved list.

2. ManualCreatePanel's switchToAgent dropped the picked project from the carry
   payload, so flipping manual → agent silently fell back to the agent panel's
   own lastProjectId — potentially routing the issue to a different project
   than the one shown in manual mode. Forward project_id alongside prompt /
   agent_id, and add a regression test.

Co-authored-by: multica-agent <github@multica.ai>

* test(quick-create): pass new isExpanded props in stale-project tests

Main got an expand button on AgentCreatePanel via #2320 while this branch
was open, adding `isExpanded` / `setIsExpanded` to the panel's required
props. The two new stale-project tests still passed `{ onClose }` only,
which CI's typecheck (run on the main+branch merge) caught while my
local run did not.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-09 16:12:12 +08:00
Bohan Jiang
2f979ac6f0 fix(daemon): tighten quick-create prompt to drop meta-instructions and apologetic Context (#2137)
* fix(daemon): tighten quick-create prompt to drop meta-instructions and apologetic Context

The quick-create prompt was producing descriptions that:
1. Echoed routing meta-instructions ("create an issue for me", "cc @X") into
   the User request body, even though those phrases are handled by separate
   CLI flags and are not spec content.
2. Emitted a Context section to apologize for resources it could not fetch
   (e.g. an image attachment not piped through to the run), instead of
   staying silent and letting the executing agent ask the user.
3. Preserved pure conversational fillers ("对吧?", "嗯", "那个…") because the
   model treated removing them as forbidden paraphrasing.

Updates the prompt to call out each of these as explicit non-spec material
to strip before writing the description, while keeping the "high fidelity /
no paraphrasing of substantive content" invariant. Adds a regression test
that locks in the new rules at the substring level.

Co-authored-by: multica-agent <github@multica.ai>

* fix(daemon): preserve cc mention links in quick-create description

Stripping "cc @Y" wholesale would have lost the mentioned member's only
routing channel: `multica issue create` has no --subscriber/--cc flag, and
the platform auto-subscribes members by parsing `[@Name](mention://member/<uuid>)`
links from the description body. Without the mention link in the body, a
cc'd member would never get subscribed or notified.

Updates the prompt to:
- Strip only the verbal "cc" wrapper from the User request body.
- Append a trailing `CC: <mention links>` line to the description so the
  platform's auto-subscribe logic still picks the mentions up.
- Spell out the contrast for assignee mentions, where --assignee-id is
  the routing channel and the body should not double-encode the mention.

Also adds a substring assertion for the "Pure conversational fillers" rule
that was missing from the original regression test.

Co-authored-by: multica-agent <github@multica.ai>

* refactor(daemon): trim quick-create prompt rules to general principles

Reviewers pointed out the previous rewrite traded one prompt smell (over-
permissive verbatim quoting) for another (too many specific rules and
exhaustive bilingual example tables). Rewrites the description block as
general principles with a single representative example each, trusting the
model to generalize:

- "Strip non-spec material before writing" replaces the multi-bullet list
  of routing-meta-instruction and conversational-filler enumerations.
- "Include Context only when references were fetched and produced facts;
  never use it as an apology log" replaces the three "Do NOT emit a
  Context section to" sub-bullets.
- The CC exception (the only operationally non-obvious rule, since
  `multica issue create` has no --subscriber flag) is kept inline as a
  single sentence and is still locked in by the regression test.

Net: ~16 fewer lines of prompt text without losing any of the rules the
test asserts.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-06 17:57:11 +08:00
Jiayuan Zhang
d492b9d7a6 Revert "feat(quick-create): add preset issue fields (#2002)" (#2042)
This reverts commit a039c4d803.
2026-05-03 20:02:40 +02:00
ayakabot
a039c4d803 feat(quick-create): add preset issue fields (#2002)
Fixed: #2001
2026-05-03 19:37:12 +08:00