Skip to content
Open
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
8 changes: 4 additions & 4 deletions apps/presentation/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"smoke:benchmark-study": "rm -rf node_modules/.cache/loopx-benchmark-study-smoke && tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck --strict --resolveJsonModule --outDir node_modules/.cache/loopx-benchmark-study-smoke smoke/benchmark-study-contract-smoke.ts src/data/benchmark-study.ts && node node_modules/.cache/loopx-benchmark-study-smoke/smoke/benchmark-study-contract-smoke.js",
"smoke:benchmark-study-browser": "node ../../../examples/dashboard-benchmark-study-browser-smoke.mjs",
"smoke:capability-configuration": "rm -rf node_modules/.cache/loopx-capability-configuration-smoke && tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --types node --skipLibCheck --strict --outDir node_modules/.cache/loopx-capability-configuration-smoke smoke/capability-configuration-smoke.ts src/data/capability-configuration.ts && node node_modules/.cache/loopx-capability-configuration-smoke/smoke/capability-configuration-smoke.js",
"smoke:chat-route": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --ignoreDeprecations 6.0 --skipLibCheck --strict --outDir node_modules/.cache/loopx-chat-route-smoke smoke/chat-route-smoke.ts src/data/chat-model.ts src/vite-env.d.ts && node node_modules/.cache/loopx-chat-route-smoke/smoke/chat-route-smoke.js",
"smoke:chat-route": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --ignoreDeprecations 6.0 --skipLibCheck --strict --rootDir ../../.. --outDir node_modules/.cache/loopx-chat-route-smoke smoke/chat-route-smoke.ts src/data/chat-model.ts src/vite-env.d.ts && node node_modules/.cache/loopx-chat-route-smoke/apps/presentation/dashboard/smoke/chat-route-smoke.js",
"smoke:chat-stream-stall": "vite build --ssr smoke/chat-stream-stall-smoke.ts --outDir node_modules/.cache/loopx-chat-stream-stall --emptyOutDir && node node_modules/.cache/loopx-chat-stream-stall/chat-stream-stall-smoke.js",
"smoke:chat-turn-acceptance-retry": "vite build --ssr smoke/chat-turn-acceptance-retry-smoke.ts --outDir node_modules/.cache/loopx-chat-turn-acceptance-retry --emptyOutDir && node node_modules/.cache/loopx-chat-turn-acceptance-retry/chat-turn-acceptance-retry-smoke.js",
"smoke:demo-readiness": "bash ../../../scripts/loopx-python.sh --exec ../../../examples/dashboard-demo-readiness-smoke.py",
Expand All @@ -38,10 +38,10 @@
"smoke:home-route": "rm -rf /tmp/loopx-home-route-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --skipLibCheck --strict --outDir /tmp/loopx-home-route-smoke smoke/home-route-smoke.ts && node /tmp/loopx-home-route-smoke/home-route-smoke.js",
"smoke:delegation-preflight": "rm -rf node_modules/.cache/loopx-delegation-preflight-smoke && tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --jsx react-jsx --skipLibCheck --strict --rootDir . --outDir node_modules/.cache/loopx-delegation-preflight-smoke smoke/delegation-preflight-smoke.tsx src/data/delegation-preflight.ts src/features/personal-workspace/delegation-preflight-status.tsx && node node_modules/.cache/loopx-delegation-preflight-smoke/smoke/delegation-preflight-smoke.js",
"smoke:delegation-preflight-browser": "node --experimental-strip-types smoke/delegation-preflight-browser-smoke.mjs",
"smoke:personal-workspace": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser-smoke.mjs",
"smoke:personal-workspace": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/runtime-readiness.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser-smoke.mjs",
"smoke:workspace-locale": "LOOPX_PERSONAL_WORKSPACE_SCENARIO=workspace-locale node ../../../examples/personal-workspace-browser-smoke.mjs",
"smoke:personal-workspace-packaged": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs",
"smoke:personal-workspace-packaged:catalog": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs",
"smoke:personal-workspace-packaged": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/runtime-readiness.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs",
"smoke:personal-workspace-packaged:catalog": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/runtime-readiness.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/attention-notifications.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/goal-suggestions.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs",
"smoke:personal-workspace-packaged:team": "LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs",
"smoke:personal-workspace-packaged:craft": "LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs",
"smoke:todo-resume-condition": "rm -rf /tmp/loopx-todo-resume-condition-smoke && tsc --ignoreConfig --target ES2022 --module commonjs --moduleResolution node --ignoreDeprecations 6.0 --skipLibCheck --strict --outDir /tmp/loopx-todo-resume-condition-smoke smoke/todo-resume-condition-smoke.ts src/features/personal-workspace/todo-resume-condition.ts && node /tmp/loopx-todo-resume-condition-smoke/smoke/todo-resume-condition-smoke.js",
Expand Down
16 changes: 12 additions & 4 deletions apps/presentation/dashboard/smoke/chat-route-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ type ChatRouteCandidate = {
};

const capabilityAwareRoute = chatModel as unknown as {
selectAvailableChatAgent?: (
selectChatAgent?: (
options: ChatRouteCandidate[],
preferredAgentId: string | undefined,
defaultAgentId: string,
) => ChatRouteCandidate;
};

check(
typeof capabilityAwareRoute.selectAvailableChatAgent === "function",
typeof capabilityAwareRoute.selectChatAgent === "function",
"Chat routing should expose stale-selection fallback",
);

Expand All @@ -64,17 +64,25 @@ const routeOptions: ChatRouteCandidate[] = [
{ agentId: "status-only", available: true },
];
check(
capabilityAwareRoute.selectAvailableChatAgent!(routeOptions, "registered-fixer", "codex").agentId === "codex",
capabilityAwareRoute.selectChatAgent!(routeOptions, "registered-fixer", "codex").agentId === "codex",
"a persisted unavailable Agent should fall back to Codex",
);
check(
capabilityAwareRoute.selectAvailableChatAgent!(
capabilityAwareRoute.selectChatAgent!(
routeOptions.map((option) => option.agentId === "codex" ? { ...option, available: false } : option),
"registered-fixer",
"status-only",
).agentId === "status-only",
"Chat should fall back to status-only when no model adapter is available",
);
const unavailableProjectOptions = routeOptions.filter(option => option.agentId !== "status-only")
.map(option => ({ ...option, available: false }));
const diagnosticRoute = capabilityAwareRoute.selectChatAgent!(unavailableProjectOptions, "registered-fixer", "codex");
check(diagnosticRoute.agentId === "registered-fixer" && diagnosticRoute.available === false,
"An unavailable project keeps the preferred identity and its refusal, rather than crashing or granting execution");
const missingPreferred = capabilityAwareRoute.selectChatAgent!(unavailableProjectOptions, "status-only", "status-only");
check(missingPreferred.agentId === "codex" && missingPreferred.available === false,
"A project without status projection keeps an actual unavailable executor, never a fabricated readonly route");

const fixture: ChatStatus = {
ok: true,
Expand Down
10 changes: 7 additions & 3 deletions apps/presentation/dashboard/src/data/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export type ChatRouteCandidate = {
available: boolean;
};

export function selectAvailableChatAgent<T extends ChatRouteCandidate>(
/** Prefer an executable route; keep an unavailable candidate for diagnosis when none can run. */
export function selectChatAgent<T extends ChatRouteCandidate>(
options: T[],
preferredAgentId: string | undefined,
defaultAgentId: string,
Expand All @@ -118,9 +119,12 @@ export function selectAvailableChatAgent<T extends ChatRouteCandidate>(
option.agentId === preferredAgentId && option.available
) ?? options.find((option) =>
option.agentId === defaultAgentId && option.available
) ?? options.find((option) => option.available);
) ?? options.find((option) => option.available)
?? options.find((option) => option.agentId === preferredAgentId)
?? options.find((option) => option.agentId === defaultAgentId)
?? options[0];
if (!selected) {
throw new Error("Chat requires at least one available route");
throw new Error("Chat requires at least one route");
}
return selected;
}
Expand Down
6 changes: 5 additions & 1 deletion apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export {
pendingGoalReviews,
proposalReviewState,
sessionInvalidatedByPayload,
selectAvailableChatAgent,
selectChatAgent,
selectChatGoal,
stewardPrompts,
turnReplaySafeByPayload,
Expand Down Expand Up @@ -205,6 +205,10 @@ export const chatCapabilitiesSchema = z.object({
source: z.string().optional(),
tool_calls: z.boolean().optional(),
trust_scope: z.string().optional(),
// Readiness: why an unavailable built-in runtime is unavailable and what it needs.
unavailable_reason: z.string().nullable().optional(),
executable: z.string().nullable().optional(),
environment_variable: z.string().nullable().optional(),
})).optional(),
lark_cli: z.object({
available: z.boolean(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,23 @@ const en = {
"files.title": "Files & Outputs",
"files.verifiedReport": "Verified milestone report",
"header.agentUnavailable": "Unavailable",
"agent.statusAvailable": "Available",
"agent.statusDetecting": "Detecting…",
"agent.statusNeedsSetup": "Needs setup",
"agent.workspaceWriteVerified": "Verified before this Goal writes",
"agent.workspaceUnavailable": "Unavailable; repair the runtime first",
"readiness.noneTitle": "No Agent runtime is ready on this machine",
"readiness.noneLead": "LoopX drives Agents through a command-line tool installed here, such as Codex, Claude Code or Kiro CLI, or through an API key. Install and sign in to one, then check again. Nothing is installed or granted from this page.",
"readiness.missingExecutable": "{label}: command “{name}” was not found on PATH",
"readiness.missingEnvironment": "{label}: environment variable {name} is not set",
"readiness.missingOther": "{label}: not available ({reason})",
"readiness.missingUnknown": "{label}: not available",
"readiness.selectedUnavailable": "{label} is not available on this machine. Install and sign in, then check again, or pick another runtime.",
"readiness.selectedUnavailableNone": "Agent execution is not ready on this machine. Set up an execution runtime first.",
"readiness.selectedUnavailableReadonly": "Agent execution is not ready. Status only can still answer questions about existing work.",
"readiness.recheck": "Check again",
"readiness.rechecking": "Checking…",
"readiness.guide": "Setup guide",
"header.chatRuntime": "Chat",
"header.workAgentCount": "{count} work Agents",
"header.overview": "Overview",
Expand Down Expand Up @@ -2117,6 +2134,23 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"files.title": "Files & Outputs",
"files.verifiedReport": "已验证阶段周报",
"header.agentUnavailable": "不可用",
"agent.statusAvailable": "可用",
"agent.statusDetecting": "正在检测",
"agent.statusNeedsSetup": "需要配置",
"agent.workspaceWriteVerified": "当前 Goal 写入前验证",
"agent.workspaceUnavailable": "不可用,需先修复运行环境",
"readiness.noneTitle": "这台机器上还没有可用的 Agent 运行环境",
"readiness.noneLead": "LoopX 通过本机已安装的命令行工具驱动 Agent,例如 Codex、Claude Code 或 Kiro CLI,也可以通过 API 密钥。安装并登录其中一个后,点「重新检查」。本页不会安装任何东西,也不授予任何权限。",
"readiness.missingExecutable": "{label}:未在 PATH 中找到命令「{name}」",
"readiness.missingEnvironment": "{label}:未设置环境变量 {name}",
"readiness.missingOther": "{label}:不可用({reason})",
"readiness.missingUnknown": "{label}:不可用",
"readiness.selectedUnavailable": "{label} 在这台机器上不可用。安装并登录后重新检查,或选择其他运行环境。",
"readiness.selectedUnavailableNone": "Agent 执行尚未就绪,请先完成执行运行环境设置。",
"readiness.selectedUnavailableReadonly": "Agent 执行尚未就绪;仍可使用「仅查状态」查询已有工作。",
"readiness.recheck": "重新检查",
"readiness.rechecking": "正在检查…",
"readiness.guide": "安装指引",
"header.chatRuntime": "Chat",
"header.workAgentCount": "{count} 个工作 Agent",
"header.overview": "概览",
Expand Down
Loading
Loading