feat: show live worker status card to all users during debugging
Non-owners now see the same real-time status card as the owner, marked as another user's proposal, instead of a desensitized busy line. This is a debugging-phase relaxation; restore owner-only visibility for production.
This commit is contained in:
@@ -220,7 +220,7 @@ Assistant 每条回复以隐藏 `GORI_ASSISTANT_ACTION_V2` envelope 结尾(`re
|
||||
- 阻塞规则:任何 `working` 全局拒绝 start_next/follow_up;owner 自己有 pending 时拒绝该 owner 的 start_next(提示先 finish 或 follow_up);任何 `workspaceDirty: true` 的 pending 全局拒绝 start_next(提示先处理 dirty 工作区);非 dirty 的 pending 不挡其他用户。
|
||||
- `stop` 只对 working 生效:停 worker(现有进程组清理)后 Proposal 转为 pending(summary「被用户中止」、workspaceDirty)。
|
||||
- Assistant 有 pending 时每轮 prompt 注入 pending card;回复没提到 pending 时 runtime 在 reply 末尾追加人话兜底提醒。
|
||||
- Worker working 期间 runtime 按类别聚合 worker 的 `session/update` 工具活动(turn 开始重置计数;只留类别/时间,不留参数与正文);owner 的 Assistant prompt 注入紧凑状态卡(title、mm:ss 时长、最后活动类别与距今、各类别计数、快照指引),非 owner 只给脱敏 busy。
|
||||
- Worker working 期间 runtime 按类别聚合 worker 的 `session/update` 工具活动(turn 开始重置计数;只留类别/时间,不留参数与正文);Assistant prompt 注入紧凑状态卡(title、mm:ss 时长、最后活动类别与距今、各类别计数、快照指引)。调试期状态卡对所有人可见(他人任务标注 `another user's proposal`),正式运营时应恢复 owner-only。
|
||||
- Gateway 无固定时间提醒;worker 落定后由 Assistant 生成事件文案发给 owner chat,QQ 无主动消息权限时优先使用被动回复窗口(群 5 分钟/私聊 60 分钟,按 4.5/55 分钟保守判定),过期或失败则不主动发、记录 lastEventDelivery 并在下次入站时补发;`/status` 输出当前 chat 的事件投递状态与 schedulerState/blockedReason/nextAction。
|
||||
|
||||
Assistant 隔离:cwd 在实例私有 `state/assistant-workspaces/`,目录 key 使用进程随机 salt 的 HMAC,不得与项目 workspace 重叠;Kimi 项目级 agent override 必须设置 `tools: []`、`subagents: []`,ACP `mcpServers: []`,未显式配置时 `KIMI_CODE_HOME` 指向实例私有 `state/kimi/assistant/`;任何 tool update 或 permission request 都视为隔离违约,fail closed、终止进程组并删除 binding。每个 ACP worker 独立进程组 + 随机 token;runner 重启时 working Proposal 校验 token 清理旧进程组后标记为 pending(worker_lost、workspaceDirty: true)。
|
||||
|
||||
@@ -206,7 +206,7 @@ QQ 出站图片:Worker/Assistant 报告的 workspace 内图片(png/jpg、≤
|
||||
- 阻塞规则:任何 `working` 全局拒绝 `start_next`/`follow_up`;owner 自己有 `pending` 时拒绝该 owner 的 `start_next`(提示先 finish 或 follow_up);任何 `workspaceDirty` 的 `pending` 全局拒绝 `start_next`(提示先处理 dirty 工作区)。非 dirty 的 pending 不挡其他用户。
|
||||
- `stop` 只对 `working` 生效:停掉 Worker(含进程组清理)并把 Proposal 标记为 `pending`(summary 为「被用户中止」、`workspaceDirty: true`)。
|
||||
- Assistant 有 pending 时每轮 prompt 注入 pending card(id/title/summary/question);Assistant 回复没提到 pending 时,runtime 在回复末尾追加一条人话兜底提醒。
|
||||
- Worker working 期间,runtime 把 `session/update` 的工具活动按类别(read/search/write/execute/delegate/other)聚合成实时快照(不含工具参数、输出或正文);owner 的下一轮 Assistant prompt 注入紧凑状态卡(title、运行时长、最后活动类别、各类别计数),Assistant 据此用人话描述进度;非 owner 仍只见脱敏 busy。
|
||||
- Worker working 期间,runtime 把 `session/update` 的工具活动按类别(read/search/write/execute/delegate/other)聚合成实时快照(不含工具参数、输出或正文);下一轮 Assistant prompt 注入紧凑状态卡(title、运行时长、最后活动类别、各类别计数),Assistant 据此用人话描述进度。调试期状态卡对所有人可见(他人任务会标注 `another user's proposal`)。
|
||||
- Gateway 不再有 15/60/180/480 秒的固定时间提醒;Worker 落定结果时通过 Assistant 生成一条事件说明,由平台 adapter 作为**新消息**发给 owner chat(QQ 同样是新消息,不引用原消息)。
|
||||
|
||||
Assistant 隔离与旧 side Session 一致且更严格:
|
||||
|
||||
@@ -850,9 +850,9 @@ export class AssistantManager implements ConversationRuntime {
|
||||
const active = this.active;
|
||||
if (!active) return "no active worker";
|
||||
const proposal = this.proposals.get(active.proposalId);
|
||||
if (!proposal || proposal.ownerChatKey !== chatKey || proposal.requesterUserId !== userId) {
|
||||
return "busy: a worker is executing another user's proposal; details hidden";
|
||||
}
|
||||
// Debugging phase: the live status card is visible to everyone; foreign proposals are
|
||||
// marked instead of hidden. Restore owner-only visibility once privacy matters again.
|
||||
const foreign = !proposal || proposal.ownerChatKey !== chatKey || proposal.requesterUserId !== userId;
|
||||
const worker = active.worker;
|
||||
const activity = active.activity;
|
||||
const runningMs = activity.turnStartedAt ? Math.max(0, Date.now() - activity.turnStartedAt) : 0;
|
||||
@@ -865,7 +865,7 @@ export class AssistantManager implements ConversationRuntime {
|
||||
.map((category) => `${category}×${activity.counts[category]}`)
|
||||
.join(" ") || "none yet";
|
||||
return [
|
||||
`title=${JSON.stringify(proposal.title)} running=${running} phase=${worker.phase} inFlight=${worker.inFlight}`,
|
||||
`title=${JSON.stringify(proposal?.title ?? "unknown")} running=${running} phase=${worker.phase} inFlight=${worker.inFlight}${foreign ? " (another user's proposal)" : ""}`,
|
||||
`lastActivity=${lastActivity}; counts: ${counts}`,
|
||||
"guidance: this is a real-time snapshot of the worker as of this message; when the user asks about progress, paraphrase it in your own words and never invent details beyond this snapshot."
|
||||
].join("\n");
|
||||
|
||||
@@ -406,8 +406,9 @@ test("proposals are owned per chat+user: a second user in the same chat cannot c
|
||||
await harness.manager.prompt(request("hello again", "user-b"));
|
||||
const bPrompt = readLog(harness.logFile).find((entry) => entry.method === "session/prompt" && entry.text?.startsWith("[User message]\nhello again"));
|
||||
assert.ok(bPrompt?.text);
|
||||
assert.match(bPrompt.text!, /details hidden/);
|
||||
assert.doesNotMatch(bPrompt.text!, /Test proposal/);
|
||||
// Debugging phase: non-owners also see the live card, marked as another user's proposal.
|
||||
assert.match(bPrompt.text!, /Test proposal/);
|
||||
assert.match(bPrompt.text!, /another user's proposal/);
|
||||
|
||||
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-b"), false);
|
||||
assert.equal(harness.proposals.get(proposal.id)!.status, "working");
|
||||
@@ -751,9 +752,9 @@ test("the owner sees a live worker activity card while others get only a sanitiz
|
||||
assert.match(ownerPrompt, /counts: read×2 execute×1/);
|
||||
assert.match(ownerPrompt, /real-time snapshot of the worker/);
|
||||
const otherPrompt = progressPrompts[1]!.text!;
|
||||
assert.match(otherPrompt, /details hidden/);
|
||||
assert.doesNotMatch(otherPrompt, /lastActivity=/);
|
||||
assert.doesNotMatch(otherPrompt, /Test proposal/);
|
||||
// Debugging phase: non-owners see the same live card, marked as foreign.
|
||||
assert.match(otherPrompt, /title="Test proposal" running=\d{2}:\d{2} phase=processing inFlight=true \(another user's proposal\)/);
|
||||
assert.match(otherPrompt, /lastActivity=execute \(\d+s ago\)/);
|
||||
|
||||
// Once the worker is no longer working, the card disappears.
|
||||
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-a"), true);
|
||||
|
||||
Reference in New Issue
Block a user