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
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const en = {
"common.waiting": "Waiting",
"composer.addImage": "Add image",
"composer.agentProgress": "Ask Agent for a progress report",
"composer.blockers": "Ask what blocks",
"composer.agentProgressPrompt": "Give me a progress report for this Goal: completed, running, blocked, and next steps.",
"composer.attachImageHint": "Choose, paste, or drag an image",
"composer.clarifyDefer": "Deferring a Todo requires a deterministic resume condition. Add todo_done:<todo_id>, pr_merged:[owner/repo]#<number>, capacity_available:<capability>, or resume_at:<timezone-aware RFC3339 timestamp>.",
Expand All @@ -76,7 +77,7 @@ const en = {
"composer.createGoalDraftLead": "Create a long-term Goal:",
"composer.createGoalTemplate": "Create a long-term Goal:\nObjective:\nCompletion criteria:\nExecution boundary (optional):\nRelated repository (optional):\nNotification method (optional):",
"composer.createGoalHint": "Insert a Goal template to review before creation",
"composer.draft": "Draft",
"composer.evidence": "Check what is proven",
"composer.globalProgress": "Summarize all Goal progress",
"composer.globalProgressPrompt": "Summarize the latest progress and blockers for all active Goals.",
"composer.globalTasks": "Ask about global priorities",
Expand All @@ -92,20 +93,18 @@ const en = {
"composer.imageSizeError": "Each image must be {size} MB or smaller.",
"composer.imageTypeError": "PNG, JPEG, WebP, and GIF images are supported.",
"composer.imagesPending": "Images to send",
"composer.immediate": "Send now",
"composer.managerMessageHint": "Your message goes to the LoopX Manager across Goals for global questions or Goal creation.",
"composer.managerPlaceholder": "Ask the LoopX Manager, or describe a new Goal…",
"composer.monitor": "Configure scheduled check",
"composer.monitorGoalQuestion": "Which Goal should receive the scheduled check?",
"composer.monitorTemplate": "Add a scheduled check for the current Goal:\nCheck target:\nFrequency (supports 30 minutes / 2 hours / daily): Every 2 hours\nStop condition: Goal completes",
"composer.monitorShortcutTemplate": "Add a scheduled check for the current Goal:\nCheck target: {target}\nFrequency: Every 2 hours\nStop condition: Goal completes",
"composer.monitorTemplateWithoutGoal": "Configure a scheduled check:\nGoal:\nCheck target:\nFrequency: Every 2 hours\nStop condition: Goal completes",
"composer.monitorHint": "Fill in what to check, frequency, and stop condition before creation",
"composer.heartbeatGoalQuestion": "Which Goal should receive the Heartbeat?",
"composer.heartbeatTemplate": "Set a Heartbeat for the current Goal:\nFrequency: Daily\nStop condition: Goal completes\nNotification: Only notify me when needed",
"composer.heartbeatTemplateWithoutGoal": "Set up a Heartbeat:\nGoal:\nFrequency: Daily\nStop condition: Goal completes\nNotification: Only notify me when needed",
"composer.nextAction": "Ask what’s next",
"composer.nextActionPrompt": "What should I do next?",
"composer.prepareDraft": "Insert a draft and review before sending",
"composer.send": "Send",
"composer.sendMessage": "Send a message to LoopX",
"composer.sendMessageHint": "Send message",
Expand Down Expand Up @@ -1105,16 +1104,17 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"composer.addImage": "添加图片",
"composer.agentProgress": "向 Agent 获取进度报告",
"composer.agentProgressPrompt": "请给我一份当前 Goal 的进度报告:已完成、执行中、阻塞和下一步。",
"composer.blockers": "看阻塞",
"composer.attachImageHint": "选择、粘贴或拖入图片",
"composer.clarifyDefer": "暂缓 Todo 需要可自动判断的恢复条件。请补充 todo_done:<todo_id>、pr_merged:[owner/repo]#<number>、capacity_available:<capability> 或 resume_at:<带时区 RFC3339 时间>。",
"composer.clarifySingleAction": "这句话包含多个可能修改状态的操作。请一次描述一项操作,我会逐项展示确认预览。",
"composer.evidence": "查证据",
"composer.createGoal": "创建新 Goal",
"composer.createGoalDraft": "Goal 草稿",
"composer.createGoalDraftDescription": "补充内容后发送,LoopX 会先展示待确认操作。",
"composer.createGoalDraftLead": "我想创建一个长期 Goal:",
"composer.createGoalTemplate": "我想创建一个长期 Goal:\n目标:\n完成标准:\n执行边界(可选):\n关联仓库(可选):\n通知方式(可选):",
"composer.createGoalHint": "填入 Goal 模板草稿,检查后再创建",
"composer.draft": "草稿",
"composer.globalProgress": "汇总所有 Goal 进展",
"composer.globalProgressPrompt": "请帮我汇总所有活跃 Goal 的最新进展与阻塞。",
"composer.globalTasks": "询问全局待办",
Expand All @@ -1130,20 +1130,18 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"composer.imageSizeError": "单张图片不能超过 {size}MB。",
"composer.imageTypeError": "支持 PNG、JPEG、WebP 和 GIF 图片。",
"composer.imagesPending": "待发送图片",
"composer.immediate": "立即发送",
"composer.managerMessageHint": "你的消息由 LoopX 管家跨 Goal 接收,支持全局询问与创建 Goal",
"composer.managerPlaceholder": "问问 LoopX 管家,或描述一个新 Goal…",
"composer.monitor": "配置定时检查",
"composer.monitorGoalQuestion": "为哪个 Goal 添加定时检查?",
"composer.monitorTemplate": "为当前 Goal 添加定时检查:\n检查内容:\n频率(支持 30 分钟 / 2 小时 / 每天):每 2 小时\n停止条件:Goal 完成",
"composer.monitorShortcutTemplate": "为当前 Goal 添加定时检查:\n检查内容:{target}\n频率:每 2 小时\n停止条件:Goal 完成",
"composer.monitorTemplateWithoutGoal": "配置定时检查:\nGoal:\n检查内容:\n频率:每 2 小时\n停止条件:Goal 完成",
"composer.monitorHint": "先填写检查内容、频率和停止条件,不会立即创建",
"composer.heartbeatGoalQuestion": "为哪个 Goal 设置 Heartbeat?",
"composer.heartbeatTemplate": "为当前 Goal 设置 Heartbeat:\n频率:每天\n停止条件:Goal 完成\n通知:仅在需要我时",
"composer.heartbeatTemplateWithoutGoal": "设置 Heartbeat:\nGoal:\n频率:每天\n停止条件:Goal 完成\n通知:仅在需要我时",
"composer.nextAction": "询问下一步",
"composer.nextActionPrompt": "我现在该做什么?",
"composer.prepareDraft": "填入编辑框,确认后再发送",
"composer.send": "发送",
"composer.sendMessage": "向 LoopX 发送消息",
"composer.sendMessageHint": "发送消息",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ assert.match(timeline, /personal-message-images/, "Sent images remain visible in
assert.match(dashboard, /attachments: route\?\.attachments/, "Image attachments enter the selected Agent Session");
assert.match(page, /sendMessage\(t\("composer\.agentProgressPrompt"\)\)/, "Progress report shortcut sends a scoped read-only request immediately");
assert.match(page, /t\("composer\.agentProgress"\)/, "Progress report shortcut makes its immediate-send behavior explicit");
assert.match(page, /t\("composer\.nextAction"\)[\s\S]*t\("composer\.prepareDraft"\)/, "Advice shortcut explains that it only prepares a draft");
assert.match(page, /t\("composer\.monitor"\)[\s\S]*t\("composer\.monitorHint"\)/, "Monitor shortcut explains its editable-draft boundary");
assert.match(page, /t\("composer\.nextAction"\)[\s\S]*sendMessage\(t\("composer\.nextActionPrompt"\)\)/, "Advice shortcut sends its scoped question immediately");
assert.match(page, /t\("composer\.monitor"\)[\s\S]*sendMessage\(t\("composer\.monitorShortcutTemplate", \{ target: t\("schedule\.defaultTarget"\) \}\)\)/, "Monitor shortcut sends its bounded template with a named check target immediately");
assert.match(page, /goalDraftActive[\s\S]*t\("composer\.createGoalDraft"\)[\s\S]*t\("composer\.createGoal"/, "Create Goal mode is visibly distinct from a normal chat draft");
assert.match(page, /setComposerDraft\(`manager:\$\{selectedAgentId\}`,\s*t\("composer\.createGoalTemplate"\)\)/, "Create Goal writes the localized template to the manager draft even when invoked from a Goal");
assert.match(page, /personal-action-feedback/, "Typed actions surface a persistent visible receipt");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
listTypedActions,
previewTypedAction,
setupGoalChannel,
stewardPrompts,
transitionTypedAction,
type GoalRepositoryContext,
type LarkGoalConnection,
Expand Down Expand Up @@ -853,10 +854,10 @@
function setComposer(value: string) {
setComposerDraft(composerDraftKey, value);
}
function fillQuickPrompt(text: string) {
const existing = drafts[composerDraftKey]?.trimEnd();
setComposer(existing ? `${existing}\n${text}` : text);
window.requestAnimationFrame(() => composerRef.current?.focus());
// The steward prompt set is owned by the client model; the quick-prompt row
// reuses it so one affordance answers "what now / what blocks / what is proven".
function stewardPromptText(id: string) {

Check warning on line 859 in apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'stewardPromptText' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaCtFuzCh0Z28k2fazbG&open=AaCtFuzCh0Z28k2fazbG&pullRequest=4591
return stewardPrompts.find((item) => item.id === id)?.prompt ?? "";
}
useEffect(() => {
const el = composerRef.current;
Expand Down Expand Up @@ -2003,15 +2004,17 @@
</p>
{selectedGoal ? (
<div className="personal-quick-prompts">
<button aria-label={t("composer.nextAction")} className="is-draft" onClick={() => fillQuickPrompt(t("composer.nextActionPrompt"))} title={t("composer.prepareDraft")} type="button"><MessageCircleQuestion size={13} /><span>{t("composer.nextAction")}</span><small className="personal-prompt-subtle">{t("composer.draft")}</small></button>
<button className="is-immediate" disabled={sending} onClick={() => void sendMessage(t("composer.agentProgressPrompt"))} title={t("composer.immediate")} type="button"><Send size={13} /><span>{t("composer.agentProgress")}</span><em className="personal-prompt-badge">{t("composer.immediate")}</em></button>
<button aria-label={t("composer.monitor")} className="is-draft" onClick={() => prepareScheduleDraft("monitor", selectedGoalId)} title={t("composer.monitorHint")} type="button"><CalendarClock size={13} /><span>{t("composer.monitor")}</span><small className="personal-prompt-subtle">{t("composer.draft")}</small></button>
<button aria-label={t("composer.nextAction")} disabled={sending} onClick={() => void sendMessage(t("composer.nextActionPrompt"))} title={t("composer.sendMessageHint")} type="button"><MessageCircleQuestion size={13} /><span>{t("composer.nextAction")}</span></button>
<button aria-label={t("composer.agentProgress")} disabled={sending} onClick={() => void sendMessage(t("composer.agentProgressPrompt"))} title={t("composer.sendMessageHint")} type="button"><Send size={13} /><span>{t("composer.agentProgress")}</span></button>
<button aria-label={t("composer.monitor")} disabled={sending} onClick={() => void sendMessage(t("composer.monitorShortcutTemplate", { target: t("schedule.defaultTarget") }))} title={t("composer.sendMessageHint")} type="button"><CalendarClock size={13} /><span>{t("composer.monitor")}</span></button>
<button aria-label={t("composer.blockers")} disabled={sending || !stewardPromptText("gate")} onClick={() => void sendMessage(stewardPromptText("gate"))} title={t("composer.sendMessageHint")} type="button"><AlertCircle size={13} /><span>{t("composer.blockers")}</span></button>
<button aria-label={t("composer.evidence")} disabled={sending || !stewardPromptText("evidence")} onClick={() => void sendMessage(stewardPromptText("evidence"))} title={t("composer.sendMessageHint")} type="button"><FileText size={13} /><span>{t("composer.evidence")}</span></button>
</div>
) : (
<div className="personal-quick-prompts">
<button aria-label={t("composer.globalTasks")} className="is-draft" onClick={() => fillQuickPrompt(t("composer.globalTasksPrompt"))} title={t("composer.prepareDraft")} type="button"><MessageCircleQuestion size={13} /><span>{t("composer.globalTasks")}</span><small className="personal-prompt-subtle">{t("composer.draft")}</small></button>
<button className="is-immediate" disabled={sending} onClick={() => void sendMessage(t("composer.globalProgressPrompt"))} title={t("composer.immediate")} type="button"><Send size={13} /><span>{t("composer.globalProgress")}</span><em className="personal-prompt-badge">{t("composer.immediate")}</em></button>
<button aria-label={t("composer.createGoal")} className="is-draft" onClick={requestGoalCreate} title={t("composer.createGoalHint")} type="button"><Plus size={13} /><span>{t("composer.createGoal")}</span><small className="personal-prompt-subtle">{t("composer.draft")}</small></button>
<button aria-label={t("composer.globalTasks")} disabled={sending} onClick={() => void sendMessage(t("composer.globalTasksPrompt"))} title={t("composer.sendMessageHint")} type="button"><MessageCircleQuestion size={13} /><span>{t("composer.globalTasks")}</span></button>
<button aria-label={t("composer.globalProgress")} disabled={sending} onClick={() => void sendMessage(t("composer.globalProgressPrompt"))} title={t("composer.sendMessageHint")} type="button"><Send size={13} /><span>{t("composer.globalProgress")}</span></button>
<button aria-label={t("composer.createGoal")} onClick={requestGoalCreate} title={t("composer.createGoalHint")} type="button"><Plus size={13} /><span>{t("composer.createGoal")}</span></button>
</div>
)}
{goalDraftActive ? <div className="personal-goal-draft-status" role="status"><strong>{t("composer.createGoalDraft")}</strong><span>{t("composer.createGoalDraftDescription")}</span></div> : null}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading