Skip to content

fix(host): unknown-workspaceId 报错自带可用 id,workspaceId 接受项目路径(#58) - #59

Merged
cloader merged 3 commits into
cloader:mainfrom
Ausli:feat/workspace-id-correction-signal
Oct 25, 2026
Merged

cloader merged 3 commits into
cloader:mainfrom
Ausli:feat/workspace-id-correction-signal

Conversation

@Ausli

@Ausli Ausli commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

现象

#52 之后传路径不再静默返回 0 条,改成报错(正确)。但报错本身没有可用的纠正信号:

taskboard_list({ workspaceId: "G:\\workarea\\MyProjectTemp\\KaoYan" })
→ Error: not_found: unknown workspaceId G:\workarea\MyProjectTemp\KaoYan

调用方知道自己会话的 cwd,但注册表以 uuid 为键、工具面没有「我属于哪个项目」的自查询,于是它拿不到任何可用 id,只能再猜一次。

更隐蔽的一层:即使详情里写出了 id,id (title) 这种展示拼接也是任何 resolver 都不接受的拼写——调用方照抄即再次失败,「纠错信号」形同虚设。

三个提交

按 #58 里的讨论与维护者「就用 B」的决定,采用「报错自带本会话项目(A)+ workspaceId 接受项目目录路径(B)」的组合,并把详情收敛成可原样回填的形态:

# hash 标题
1 301f22a fix(host): name the caller's project in unknown-workspaceId errors
2 184d072 feat(host): accept a project path for workspaceId in list/create
3 6954a7f fix(host): make the unknown-workspaceId detail reusable
  1. 消息(301f22a):失败详情点名调用方自己的项目。纯文本改动,拒绝行为、错误码、只读保证全不动,既有断言一条不改。
  2. 别名(184d072):workspaceId 接受项目目录路径。走 claim 边界本来就在用的 resolveByPath;list 用解析后的 id 过滤,create 存解析后的 id(路径形状入参不进台账)。按 id 优先,路径只是别名。
  3. 详情收敛(6954a7f):id 以赋值形态 workspaceId=<id> 给出,title 退为隔离旁注,去掉 own.path。

三个提交只改 src/host/tools.ts 与 tests/list-filters.spec.ts,可分别 review / 丢弃。

提交 3 的实测依据

初版详情把两者拼成 this session's project is ws-a (A)。把该串原样回传给同一个工具:

传入 workspaceId 结果
ws-a 正常,rows=2
/proj/a 正常,rows=2
ws-a (A) not_found
this session's project is ws-a (A) not_found

即菜单上的答案不可用。改为:

unknown workspaceId "ws-a (A)" (expected a project id or its directory path);
this session's project is "A"; retry with workspaceId=ws-a

workspaceId=<id> 独立成赋值形态、句末无标点,/workspaceId=(\S+)/ 摘取干净;title 用双引号隔离,即使被连带抄走也不影响取值。

own.path 一并去掉,理由三条:

  • 它是调用方自身 cwd 的回声(该分支只由 callerWorkspace() 用 cwd 解析出来),信息增量为零;
  • 它是用户可编辑的无界文本,未裁剪会破坏 title 上的 clipTitle 收敛(单行 + 60 字符);
  • list 过滤是纯台账比对,目录被移走后 id 仍可用、path 必然失效——给出 path 等于推荐一个会坏的值。

验证

命令 结果
npx tsc --noEmit exit 0
npx vitest run(全量) 43 files / 573 tests 全部通过
node verify.mjs lib/host/tools.js(构建产物回灌,R1–R9 + 路径别名 + 纠正信号) 11/11 PASS

新增 R8c 是本 PR 的关键用例:从详情里正则提取 workspaceId=(\S+),把捕获组直接回传 taskboard_list,断言 rows === 2。既有用例全部只断报文形状,没有一条断「报文里的值真能用」,上面那个菜单不可用的问题正是这样漏过去的。

R8b 覆盖用户可编辑 title 的单行化 + 60 字符截断,防止换行/超长文本进上下文。

刻意不动的地方

  • 未注册 uuid / 路径依然报错,不退回 0 条:0 条只代表「这个项目真的没任务」。
  • 已注册但无任务的项目仍返回 0 条;合法跨项目 id 照旧过滤,不按调用方所属项目收紧。
  • 空参继续全量;所有失败路径仍然只读(R7 断言 revision 不变)。
  • 无调用方身份的兜底仍是 known projects: <id>, ... 裸列表,未改成同样的赋值形态。
  • 项目标题内若含双引号会破坏旁注的引号配对,未处理(clipTitle 只压平空白与截断)。
  • 更彻底的做法是把 callerWorkspace() 暴露成只读自查询工具;本 PR 只补入参侧与失败侧的纠正能力,可作为后续单独一条。

refs #58

Ausli added 3 commits October 24, 2026 16:28
cloader#52 made an unknown `workspaceId` fail loudly instead of silently matching
nothing, but a bare `unknown workspaceId <x>` still strands the caller: the
registry keys projects by uuid, no tool answers "which project am I in", and the
error just repeats back the value that was already known to be wrong.

Name the expected shape, then hand back the calling session's own project
(`callerWorkspace()` already resolves cwd) and, when that is unavailable (cwd in
a subdirectory or in an unregistered directory), the registered projects — capped
at 8 with a `+N more` tail. That is enough to correct in one round trip.

Message-only: the rejection itself, its error code, and the read-only guarantee
are untouched, and every existing assertion in tests/list-filters.spec.ts still
holds as written.

tests: R8/R9/R10 pin the detail (own project, unaffiliated fallback, create).
Builds on the previous commit, which makes a miss self-explaining. This one
removes the miss for the common case: a model passes its session cwd because
that is the only project identity it can see, and `resolveByPath` — the resolver
the claim boundary already uses — turns that path into the id.

- `resolveWorkspaceArg`: registered id first, then `resolveByPath`. A `realpath`
  rejection means "not a project", not a tool failure, so it stays a plain miss.
- `taskboard_list` filters on the resolved id; `taskboard_create` stores it, so a
  path-shaped argument can never reach the ledger.
- Both parameter descriptions now say the path spelling is accepted, and the
  miss detail says "expected a project id or its directory path".

Unchanged: an unresolvable id or path still fails with `not_found` (never zero
rows), a registered project without tasks is still a legitimate zero, a valid
cross-project id still filters, and refusals stay read-only.

tests: R2 is rewritten (a registered path resolves and filters), R2b/R2c pin the
unregistered path and the realpath-rejection arm, R11 covers create.
The detail rendered the calling session's project as `id (title)` — a spelling
no resolver accepts, so a caller that copied the correction signal verbatim
failed again on the next round trip.

Render the id as an assignment instead (`retry with workspaceId=<id>`), never
inside a parenthesis, and keep the title as an isolated quoted note: it stays
readable for a human (ids are uuids) without touching the value's
recoverability.

Drop `own.path`. It echoes the caller's own cwd (this branch only exists when
`callerWorkspace()` resolved that cwd), it is unbounded user-editable text that
would undo the title clipping, and it recommends a value that starts failing the
moment the directory is unreachable — list filtering is ledger-only, so the id
keeps working then and a path does not.

tests: R8/R8b updated, R8c added — extract `workspaceId=(\S+)` from the detail,
feed it straight back to `taskboard_list`, assert rows === 2.
@cloader
cloader merged commit 751dd34 into cloader:main Oct 25, 2026
1 check passed
@Ausli
Ausli deleted the feat/workspace-id-correction-signal branch October 25, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants