@zq-silk/yui 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +25 -11
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +30 -9
- package/dist/commands/taskIntegrationCommands.js +5 -16
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +51 -39
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +15 -79
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +86 -80
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +18 -11
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +306 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +114 -9
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +4 -3
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
- package/dist/web/assets/client/app.js +0 -804
- package/dist/web/assets/client/components.js +0 -418
- package/dist/web/assets/client/detail.js +0 -333
- package/dist/web/assets/client/dock.js +0 -225
- package/dist/web/assets/client/evidence.js +0 -172
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1161
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -63
- package/dist/web/assets/client/sections.js +0 -313
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -169
- package/dist/web/assets/styles/views.js +0 -292
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DELIVERY VIEW — the Delivery tab's results: delivery projects and their
|
|
3
|
+
* check lists, plus evidence files and the artifact viewer.
|
|
4
|
+
*/
|
|
5
|
+
export const DELIVERY_STYLES = `
|
|
6
|
+
/* Delivery results */
|
|
7
|
+
.delivery-stack>.sub-head:not(:first-child){margin-top:10px}
|
|
8
|
+
.delivery-project .record-head strong{overflow-wrap:anywhere}
|
|
9
|
+
.check-list{list-style:none;margin:0;padding:0;display:grid;gap:4px;font-size:var(--fs-xs)}
|
|
10
|
+
.check-list li{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:2px 8px;min-width:0}
|
|
11
|
+
.check-details{grid-column:2/-1;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
12
|
+
|
|
13
|
+
/* Evidence files */
|
|
14
|
+
.file-list{display:grid;grid-template-columns:minmax(0,1fr);gap:2px}
|
|
15
|
+
.file-list:empty{display:none}
|
|
16
|
+
.file-row{display:flex;align-items:center;gap:8px;min-height:32px;padding:5px 8px;border:0;border-radius:var(--r-sm);background:transparent;text-align:left;font-size:var(--fs-sm);color:var(--ink-2)}
|
|
17
|
+
.file-row:hover{background:var(--surface-3);color:var(--ink)}
|
|
18
|
+
.file-row[aria-current="true"]{background:var(--accent-soft);color:var(--ink)}
|
|
19
|
+
.file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);font-size:var(--fs-xs)}
|
|
20
|
+
.file-viewer{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:8px}
|
|
21
|
+
.file-viewer:empty{display:none}
|
|
22
|
+
.viewer-head{display:flex;align-items:center;gap:8px;font-size:var(--fs-sm)}
|
|
23
|
+
.viewer-ref code{white-space:normal;overflow-wrap:anywhere}
|
|
24
|
+
.artifact-text{max-height:480px;color:var(--ink)}
|
|
25
|
+
.record-actions{flex-wrap:wrap}
|
|
26
|
+
.record-actions input{min-width:0;max-width:100%;flex:1 1 180px}
|
|
27
|
+
.composer-wrap .row-stack{overflow-wrap:anywhere;max-height:180px;overflow:auto}
|
|
28
|
+
.composer-wrap input[type=file]{max-width:100%}
|
|
29
|
+
`;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DOCK VIEW — the Discussion pane: its sub-header, the message feed with day
|
|
3
|
+
* separators and bubbles, the composer, submission facets and next steps.
|
|
4
|
+
* The Live session pane is views/terminal; the dock frame is layout/workspace.
|
|
5
|
+
*/
|
|
6
|
+
export const DOCK_STYLES = `
|
|
7
|
+
.dock-head{height:auto;min-height:var(--header-h);padding-top:6px;padding-bottom:6px}
|
|
8
|
+
.dock-head .seg{min-width:0;flex-wrap:wrap}
|
|
9
|
+
.dock-actions{flex-shrink:0}
|
|
10
|
+
.dock-preview{display:flex;flex-direction:column;min-height:0;overflow:auto}
|
|
11
|
+
.dock-preview>.feed-note{text-align:left;padding:0 12px;color:var(--ink-3)}
|
|
12
|
+
.preview-services{flex:none;max-height:40%;overflow:auto}
|
|
13
|
+
.preview-service{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--line)}
|
|
14
|
+
.preview-service small{flex-basis:100%;overflow-wrap:anywhere;color:var(--ink-3)}
|
|
15
|
+
.preview-frame{flex:1 0 240px;width:100%;min-height:240px;border:0;background:white}
|
|
16
|
+
/* Feed */
|
|
17
|
+
.dock-sub{flex:none;display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 10px;padding:12px 16px 10px;border-bottom:1px solid var(--line)}
|
|
18
|
+
.dock-sub-title{font-weight:600;font-size:var(--fs-sm)}
|
|
19
|
+
.dock-sub-note{color:var(--ink-4);font-size:var(--fs-2xs)}
|
|
20
|
+
.feed{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:14px;padding:16px;scrollbar-gutter:stable}
|
|
21
|
+
.feed:focus-visible{box-shadow:inset var(--focus)}
|
|
22
|
+
.feed>.empty{margin:auto 0}
|
|
23
|
+
.feed-note{color:var(--ink-4);font-size:var(--fs-2xs);text-align:center}
|
|
24
|
+
.feed-older{align-self:center}
|
|
25
|
+
#dock-conversation .dock-sub select{min-width:0;max-width:100%;flex:1 1 180px}
|
|
26
|
+
#dock-conversation .feed-note{overflow-wrap:anywhere;margin:4px 12px}
|
|
27
|
+
#dock-conversation .composer-bar{flex-wrap:wrap}
|
|
28
|
+
#dock-conversation .composer-input{width:100%;box-sizing:border-box}
|
|
29
|
+
#dock-conversation .composer-wrap{max-height:60%;overflow-y:auto}
|
|
30
|
+
#dock-conversation .composer-wrap select{min-width:0;max-width:100%;flex:1 1 140px}
|
|
31
|
+
#dock-conversation .composer-wrap pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:160px;overflow:auto}
|
|
32
|
+
.conversation-slash{display:flex;flex-wrap:wrap;gap:4px}
|
|
33
|
+
.conversation-slash:empty{display:none}
|
|
34
|
+
#dock-conversation .feed details{min-width:0;overflow-wrap:anywhere}
|
|
35
|
+
.conversation-requests{flex:0 1 auto;max-height:35%;overflow-y:auto;overflow-wrap:anywhere}
|
|
36
|
+
.conversation-requests h3{font-size:var(--fs-sm);margin:8px 12px}
|
|
37
|
+
.conversation-requests label{display:grid;gap:4px;margin:8px 0}
|
|
38
|
+
.conversation-requests input{min-width:0;width:100%;box-sizing:border-box}
|
|
39
|
+
.conversation-requests .input-card{margin:8px 12px}
|
|
40
|
+
.feed-day{display:flex;align-items:center;gap:10px;color:var(--ink-4);font-size:var(--fs-2xs);font-weight:500}
|
|
41
|
+
.feed-day::before,.feed-day::after{content:"";flex:1;height:1px;background:var(--line)}
|
|
42
|
+
|
|
43
|
+
/* Messages: Agent and system on the left, the user mirrored on the right */
|
|
44
|
+
.msg{display:flex;gap:10px;max-width:100%;min-width:0}
|
|
45
|
+
.msg-avatar{width:28px;height:28px;border-radius:8px;background:var(--surface-3);color:var(--ink-2);font-size:var(--fs-xs);font-weight:650}
|
|
46
|
+
.msg-main{display:grid;gap:5px;min-width:0;flex:1}
|
|
47
|
+
.msg-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:var(--fs-xs)}
|
|
48
|
+
.msg-head strong{font-weight:600}
|
|
49
|
+
.msg-head time{margin-left:auto;color:var(--ink-4);font-size:var(--fs-2xs)}
|
|
50
|
+
.msg-bubble{padding:10px 12px;border-radius:4px var(--r-lg) var(--r-lg) var(--r-lg);background:var(--surface-2);border:1px solid var(--line);min-width:0;overflow-wrap:anywhere}
|
|
51
|
+
.msg.from-user{flex-direction:row-reverse}
|
|
52
|
+
.msg.from-user .msg-head{flex-direction:row-reverse}
|
|
53
|
+
.msg.from-user .msg-head time{margin-left:0;margin-right:auto}
|
|
54
|
+
.msg.from-user .msg-avatar{background:var(--accent);color:var(--accent-ink)}
|
|
55
|
+
.msg.from-user .msg-bubble{border-radius:var(--r-lg) 4px var(--r-lg) var(--r-lg);background:var(--accent-soft);border-color:var(--accent-line)}
|
|
56
|
+
.msg.from-system .msg-bubble{background:transparent;border-style:dashed}
|
|
57
|
+
|
|
58
|
+
/* Composer, submission facets and next steps */
|
|
59
|
+
.composer-wrap{flex:none;padding:10px 12px 12px;border-top:1px solid var(--line);background:var(--surface)}
|
|
60
|
+
.composer{display:grid;gap:6px}
|
|
61
|
+
.composer-box{border:1px solid var(--line-2);border-radius:var(--r-lg);background:var(--sunken);transition:border-color var(--t-fast),box-shadow var(--t-fast)}
|
|
62
|
+
.composer-box:focus-within{border-color:var(--accent-line);box-shadow:0 0 0 3px var(--accent-soft)}
|
|
63
|
+
.composer-input{display:block;min-height:64px;max-height:240px;padding:10px 12px 4px;border:0;background:transparent;resize:none}
|
|
64
|
+
.composer-input:focus,.composer-input:hover{border:0;box-shadow:none;background:transparent}
|
|
65
|
+
.composer-bar{display:flex;align-items:center;gap:8px;padding:6px 6px 6px 8px}
|
|
66
|
+
.composer-send{height:30px}
|
|
67
|
+
.facets:empty{display:none}
|
|
68
|
+
.facets{display:grid;gap:6px;padding:8px 10px;border-radius:var(--r-sm);background:var(--surface-2)}
|
|
69
|
+
.next-step{display:flex;gap:6px;align-items:flex-start;font-size:var(--fs-xs);color:var(--ink-2)}
|
|
70
|
+
.next-step .icon{margin-top:2px;color:var(--accent)}
|
|
71
|
+
`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PAGE VIEW — the centred reading column shared by the overview and the task
|
|
3
|
+
* page, its heading scaffolding, and the overview's metric strip and rows.
|
|
4
|
+
* The overview and every task tab are a single column; nothing pairs cards
|
|
5
|
+
* side by side.
|
|
6
|
+
*/
|
|
7
|
+
export const PAGE_STYLES = `
|
|
8
|
+
.page{max-width:var(--center-max);margin:0 auto;padding:0 var(--gutter) 64px}
|
|
9
|
+
|
|
10
|
+
/* Page scaffolding */
|
|
11
|
+
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;padding:36px 0 24px}
|
|
12
|
+
.kicker{color:var(--accent);font-size:var(--fs-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.1em}
|
|
13
|
+
.page-title{margin-top:6px;font-size:var(--fs-2xl);font-weight:650;letter-spacing:-.025em}
|
|
14
|
+
.page-sub{margin-top:4px;color:var(--ink-3)}
|
|
15
|
+
.page-foot{margin-top:20px;color:var(--ink-4);font-size:var(--fs-xs);text-align:center}
|
|
16
|
+
.sub-head{display:flex;align-items:center;gap:8px;margin-top:6px;font-size:var(--fs-sm);font-weight:600}
|
|
17
|
+
|
|
18
|
+
/* Overview */
|
|
19
|
+
.metric-strip{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin-bottom:16px}
|
|
20
|
+
.metric-strip .metric{padding:14px 16px;border-radius:var(--r-lg)}
|
|
21
|
+
.metric-strip .metric-value{font-size:var(--fs-2xl)}
|
|
22
|
+
.row-list{display:grid;gap:2px;margin:-4px -8px}
|
|
23
|
+
.list-row{display:flex;align-items:center;gap:10px;width:100%;min-height:38px;padding:7px 8px;border:0;border-radius:var(--r-sm);background:transparent;color:var(--ink);text-align:left;font-size:var(--fs-sm)}
|
|
24
|
+
.list-row:hover:not(:disabled){background:var(--surface-3)}
|
|
25
|
+
.list-row:disabled{opacity:.45;cursor:default}
|
|
26
|
+
.list-row[aria-pressed="true"]{background:var(--accent-soft)}
|
|
27
|
+
.list-row-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
28
|
+
.list-row-count{font-weight:600;font-variant-numeric:tabular-nums}
|
|
29
|
+
.list-row-go{display:inline-flex;align-items:center;gap:2px;color:var(--accent);font-size:var(--fs-xs);font-weight:500}
|
|
30
|
+
.list-row-wrap{flex-wrap:wrap}
|
|
31
|
+
.list-row-wrap .chip-row{flex-basis:100%}
|
|
32
|
+
`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RECORDS VIEW — rows rendered inside list cards across the task tabs: work
|
|
3
|
+
* items, records, runs, reviews, role cards and the history timeline.
|
|
4
|
+
*/
|
|
5
|
+
export const RECORD_STYLES = `
|
|
6
|
+
/* Work items (rows of the Work list card) */
|
|
7
|
+
.work-card{min-width:0}
|
|
8
|
+
.work-head{display:flex;align-items:center;gap:10px;padding:13px 16px;list-style:none}
|
|
9
|
+
summary.work-head{cursor:pointer}
|
|
10
|
+
summary.work-head::-webkit-details-marker{display:none}
|
|
11
|
+
summary.work-head:hover .work-title{color:var(--ink)}
|
|
12
|
+
summary.work-head:focus-visible{box-shadow:inset 0 0 0 2px var(--accent-line)}
|
|
13
|
+
.work-title{flex:1;min-width:0;font-weight:600;font-size:var(--fs-md);overflow-wrap:anywhere}
|
|
14
|
+
.work-body{display:grid;gap:12px;padding:0 16px 16px 34px}
|
|
15
|
+
.work-card.is-settled .work-title{color:var(--ink-2);font-weight:500}
|
|
16
|
+
.meta-line{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;color:var(--ink-3);font-size:var(--fs-xs)}
|
|
17
|
+
.meta-strong{display:inline-flex;align-items:center;gap:4px;color:var(--ink-2);font-weight:500}
|
|
18
|
+
.meta-label{color:var(--ink-4)}
|
|
19
|
+
.candidates{margin:0;padding:0;list-style:none;display:grid;gap:4px;font-size:var(--fs-xs)}
|
|
20
|
+
.candidates li{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline}
|
|
21
|
+
.candidate-seq{font-family:var(--font-mono);color:var(--accent)}
|
|
22
|
+
|
|
23
|
+
/* Records, runs, reviews */
|
|
24
|
+
.record,.run-card,.review-card{display:grid;grid-template-columns:minmax(0,1fr);gap:8px;min-width:0;overflow-wrap:anywhere;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface)}
|
|
25
|
+
.record.is-compact{padding:9px 12px;background:var(--surface-2)}
|
|
26
|
+
.record-head,.run-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0;font-size:var(--fs-sm)}
|
|
27
|
+
.record-store{color:var(--ink-3);font-size:var(--fs-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.06em}
|
|
28
|
+
.record-actions{display:flex;gap:8px}
|
|
29
|
+
.record-lead{font-weight:500;overflow-wrap:anywhere}
|
|
30
|
+
.run-role{font-weight:600}
|
|
31
|
+
|
|
32
|
+
/* Role cards: avatar head and actions on one line, details indented below */
|
|
33
|
+
.role-card{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px 12px;align-content:start;min-width:0;padding:14px 16px}
|
|
34
|
+
.role-card>*{grid-column:1/-1;min-width:0}
|
|
35
|
+
.role-card>.role-head{grid-column:1;grid-row:1}
|
|
36
|
+
.role-card>.role-actions{grid-column:2;grid-row:1;align-self:center}
|
|
37
|
+
.role-card>:not(.role-head):not(.role-actions){margin-left:42px}
|
|
38
|
+
.role-head{display:flex;align-items:center;gap:10px}
|
|
39
|
+
.role-title{display:grid;flex:1;min-width:0;line-height:1.3}
|
|
40
|
+
.role-title .faint{font-size:var(--fs-xs)}
|
|
41
|
+
.avatar{width:32px;height:32px;border-radius:9px;background:var(--surface-3);color:var(--ink-2);font-weight:650;font-size:var(--fs-sm)}
|
|
42
|
+
.role-card .kv{font-size:var(--fs-xs)}
|
|
43
|
+
.role-actions{display:flex;justify-content:flex-end;margin-right:-8px}
|
|
44
|
+
.role-session{display:grid;gap:3px;min-width:0;font-size:var(--fs-sm)}
|
|
45
|
+
.role-session-head{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px}
|
|
46
|
+
.role-session>p{color:var(--ink-2)}
|
|
47
|
+
.card-actions>[data-slot="session-counts"]:empty{display:none}
|
|
48
|
+
|
|
49
|
+
/* History timeline */
|
|
50
|
+
.timeline{list-style:none;margin:0;padding:0;position:relative;display:grid;gap:0}
|
|
51
|
+
.timeline::before{content:"";position:absolute;left:13px;top:6px;bottom:6px;width:1px;background:var(--line-2)}
|
|
52
|
+
.timeline-item{position:relative;display:grid;grid-template-columns:28px minmax(0,1fr);gap:12px;padding:0 0 18px}
|
|
53
|
+
.timeline-mark{position:relative;z-index:1;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--surface-2);border:1px solid var(--line-2);color:var(--ink-3)}
|
|
54
|
+
.kind-decision .timeline-mark{color:var(--ok);border-color:var(--ok-soft);background:var(--ok-soft)}
|
|
55
|
+
.kind-milestone .timeline-mark{color:var(--accent);border-color:var(--accent-soft);background:var(--accent-soft)}
|
|
56
|
+
.kind-run .timeline-mark{color:var(--info);border-color:var(--info-soft);background:var(--info-soft)}
|
|
57
|
+
.kind-question .timeline-mark{color:var(--warn);border-color:var(--warn-soft);background:var(--warn-soft)}
|
|
58
|
+
.timeline-body{display:grid;grid-template-columns:minmax(0,1fr);gap:6px;min-width:0;padding-top:3px;overflow-wrap:anywhere}
|
|
59
|
+
.timeline-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:var(--fs-sm)}
|
|
60
|
+
.timeline-item>.record{align-self:start;padding:4px 0 0;border:0;background:transparent}
|
|
61
|
+
.timeline .lazy-head{align-items:flex-start;padding:2px 0 0;gap:8px}
|
|
62
|
+
.timeline .lazy-head .disclosure-chevron{order:2;margin-top:4px}
|
|
63
|
+
.timeline .lazy-body{padding:10px 0 2px}
|
|
64
|
+
.timeline-summary{flex:1;display:grid;gap:4px;min-width:0}
|
|
65
|
+
.timeline-title,.timeline-preview{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
|
|
66
|
+
.timeline-preview{margin:0}
|
|
67
|
+
.lazy-row[open] .timeline-preview{display:none}
|
|
68
|
+
.timeline-empty{list-style:none}
|
|
69
|
+
.timeline .show-more{margin-left:40px}
|
|
70
|
+
`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const SETTINGS_STYLES = `
|
|
2
|
+
.settings-body{display:block;overflow:auto;height:auto;min-height:100dvh}
|
|
3
|
+
.settings-page{max-width:1040px;margin:auto;padding:32px 24px 80px;display:grid;gap:22px}
|
|
4
|
+
.settings-page p{color:var(--ink-2);font-size:var(--fs-sm);line-height:1.6;overflow-wrap:anywhere}
|
|
5
|
+
.settings-header{display:flex;gap:20px;align-items:center}
|
|
6
|
+
.settings-card{padding:22px;border:1px solid var(--line-2);border-radius:var(--r-lg);background:var(--surface);min-width:0}
|
|
7
|
+
.settings-card>summary{font-size:var(--fs-md);font-weight:600;cursor:pointer}
|
|
8
|
+
.settings-card h2{font-size:var(--fs-md);margin-bottom:16px}
|
|
9
|
+
.settings-card[open]>summary{margin-bottom:16px}
|
|
10
|
+
.settings-stack{display:grid;gap:16px;min-width:0}
|
|
11
|
+
.settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}
|
|
12
|
+
.settings-field{padding:14px 0;border-bottom:1px solid var(--line);display:grid;gap:8px;min-width:0}
|
|
13
|
+
.settings-field small{color:var(--ink-3);overflow-wrap:anywhere}
|
|
14
|
+
.settings-field small:empty,.settings-field .settings-actions:empty{display:none}
|
|
15
|
+
.settings-field textarea{width:100%;min-height:88px;resize:vertical}
|
|
16
|
+
.settings-field input,.settings-field select{width:100%;min-width:0}
|
|
17
|
+
.settings-field .settings-reset{display:flex;gap:8px;align-items:center;font-size:var(--fs-xs)}
|
|
18
|
+
.settings-reset input{width:auto}
|
|
19
|
+
.settings-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;align-items:center}
|
|
20
|
+
.settings-receipt{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
21
|
+
.settings-error{color:var(--bad)!important}
|
|
22
|
+
.settings-observation{max-height:260px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font-size:var(--fs-xs)}
|
|
23
|
+
.settings-search{position:sticky;top:0;background:var(--canvas);padding:12px 0;z-index:1}
|
|
24
|
+
.settings-page input[type=checkbox]{width:16px;height:16px;padding:0;accent-color:var(--accent)}
|
|
25
|
+
@media(max-width:640px){.settings-page{padding:18px 12px 48px}.settings-grid{grid-template-columns:1fr}.settings-card{padding:16px}}
|
|
26
|
+
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SIDEBAR VIEW — the task index: brand and sync state, search, attention
|
|
3
|
+
* pills, status filter tabs, grouped task rows, the catalog pager and the
|
|
4
|
+
* footer buttons. Region sizing lives in layout/workspace.
|
|
5
|
+
*/
|
|
6
|
+
export const SIDEBAR_STYLES = `
|
|
7
|
+
/* Brand and sync state */
|
|
8
|
+
.brand-mark{width:28px;height:28px;border-radius:8px;background:var(--accent);color:var(--accent-ink);font-size:15px;font-weight:700;line-height:1}
|
|
9
|
+
.brand-name{font-size:var(--fs-lg);font-weight:650;letter-spacing:-.02em}
|
|
10
|
+
.sync{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 8px;border-radius:var(--r-pill);background:var(--surface-3);color:var(--ink-3);font-size:var(--fs-2xs);font-variant-numeric:tabular-nums}
|
|
11
|
+
.sync i{width:6px;height:6px;border-radius:50%;background:var(--ink-4)}
|
|
12
|
+
.sync[data-state="ok"] i{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}
|
|
13
|
+
.sync[data-state="syncing"] i{background:var(--info);animation:blink 1s ease-in-out infinite}
|
|
14
|
+
.sync[data-state="error"] i{background:var(--bad);box-shadow:0 0 0 3px var(--bad-soft)}
|
|
15
|
+
.sync[data-state="error"]{color:var(--bad)}
|
|
16
|
+
@keyframes blink{50%{opacity:.35}}
|
|
17
|
+
|
|
18
|
+
/* Search and attention filters */
|
|
19
|
+
.search-field{position:relative;display:flex;align-items:center;flex:none;margin:12px 12px 0}
|
|
20
|
+
.search-field>.icon{position:absolute;left:10px;color:var(--ink-4);pointer-events:none}
|
|
21
|
+
.search-field input{height:34px;padding:0 34px 0 32px;border-radius:var(--r-md);background:var(--surface-2)}
|
|
22
|
+
.search-field input::-webkit-search-cancel-button{display:none}
|
|
23
|
+
.search-field kbd{position:absolute;right:8px}
|
|
24
|
+
.attention-list{display:flex;flex-wrap:wrap;gap:6px}
|
|
25
|
+
.attention-pill{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border:1px solid transparent;border-radius:var(--r-pill);background:var(--tone-soft);color:var(--tone);font-size:var(--fs-xs);font-weight:500}
|
|
26
|
+
.attention-pill b{font-weight:700;font-variant-numeric:tabular-nums}
|
|
27
|
+
.attention-pill:hover{border-color:var(--tone)}
|
|
28
|
+
.attention-pill[aria-pressed="true"]{background:var(--tone);color:var(--canvas)}
|
|
29
|
+
.attention-clear{display:flex;align-items:center;gap:6px;color:var(--ink-4);font-size:var(--fs-xs)}
|
|
30
|
+
.attention-clear .icon{color:var(--ok)}
|
|
31
|
+
|
|
32
|
+
/* Status filter tabs (the strip itself is a ui/controls .tabs-row) */
|
|
33
|
+
.status-tab{position:relative;display:inline-flex;align-items:center;gap:5px;flex:none;height:32px;padding:0 8px;border:0;background:transparent;color:var(--ink-3);font-size:var(--fs-xs);font-weight:500;white-space:nowrap}
|
|
34
|
+
.status-tab:hover{color:var(--ink)}
|
|
35
|
+
.status-tab[aria-pressed="true"]{color:var(--ink)}
|
|
36
|
+
.status-tab[aria-pressed="true"]::after{left:6px;right:6px;bottom:-3px}
|
|
37
|
+
.status-tab-count{color:var(--ink-4);font-size:var(--fs-2xs);font-variant-numeric:tabular-nums}
|
|
38
|
+
|
|
39
|
+
/* Task groups and rows */
|
|
40
|
+
.task-group{margin-top:8px}
|
|
41
|
+
.task-group-head{display:flex;align-items:center;gap:6px;padding:4px 8px 4px;color:var(--ink-4);font-size:var(--fs-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.07em}
|
|
42
|
+
.task-group-head .count{background:transparent;padding:0;min-width:0}
|
|
43
|
+
.task-row{position:relative;display:flex;align-items:flex-start;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:var(--r-md);background:transparent;text-align:left;transition:background var(--t-fast)}
|
|
44
|
+
.task-row>.dot{margin-top:5px}
|
|
45
|
+
.task-row:hover{background:var(--surface-3)}
|
|
46
|
+
.task-row[aria-current="true"]{background:var(--surface-3);box-shadow:inset 0 0 0 1px var(--line-2)}
|
|
47
|
+
.task-row[aria-current="true"]::before{content:"";position:absolute;left:0;top:9px;bottom:9px;width:3px;border-radius:0 3px 3px 0;background:var(--accent)}
|
|
48
|
+
.task-body{display:grid;gap:1px;flex:1;min-width:0}
|
|
49
|
+
.task-line{display:flex;align-items:center;gap:8px;min-width:0}
|
|
50
|
+
.task-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink);font-size:var(--fs-md);font-weight:500;line-height:1.35}
|
|
51
|
+
.task-time{flex:none;color:var(--ink-4);font-size:var(--fs-2xs);font-variant-numeric:tabular-nums}
|
|
52
|
+
.task-sub{gap:8px;height:18px;color:var(--ink-4);font-size:var(--fs-2xs)}
|
|
53
|
+
.task-id{font-size:var(--fs-2xs);line-height:1;color:var(--ink-3)}
|
|
54
|
+
.task-count{display:inline-flex;align-items:center;gap:3px;line-height:1;white-space:nowrap}
|
|
55
|
+
.task-count .icon{width:11px;height:11px}
|
|
56
|
+
.task-signals{display:inline-flex;gap:4px;margin-left:auto}
|
|
57
|
+
.signal{display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 6px;border-radius:var(--r-pill);background:var(--tone-soft);color:var(--tone);font-size:10.5px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
58
|
+
.signal .icon{width:11px;height:11px;stroke-width:2}
|
|
59
|
+
.list-empty{display:grid;justify-items:center;gap:8px;padding:36px 16px;color:var(--ink-4);font-size:var(--fs-sm);text-align:center}
|
|
60
|
+
.list-empty.is-error{color:var(--bad)}
|
|
61
|
+
|
|
62
|
+
/* Catalog pager and footer */
|
|
63
|
+
.pager-btn{height:28px;padding:0 10px;border:1px solid var(--line-2);border-radius:var(--r-sm);background:transparent;color:var(--ink-2);font-size:var(--fs-xs)}
|
|
64
|
+
.pager-btn:hover:not(:disabled){border-color:var(--line-3);color:var(--ink)}
|
|
65
|
+
.pager-btn:disabled{opacity:.4}
|
|
66
|
+
.pager-count{grid-column:2;text-align:center;color:var(--ink-4);font-size:var(--fs-2xs);font-variant-numeric:tabular-nums}
|
|
67
|
+
.pager-clear{height:28px}
|
|
68
|
+
.foot-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 8px;border:1px solid var(--line-2);border-radius:var(--r-sm);background:transparent;color:var(--ink-2);font-size:var(--fs-xs);font-weight:500;min-width:0}
|
|
69
|
+
.foot-btn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
70
|
+
.foot-btn:hover{color:var(--ink);border-color:var(--line-3);background:var(--surface-3)}
|
|
71
|
+
.foot-btn[aria-pressed="true"]{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft)}
|
|
72
|
+
.foot-btn kbd{margin-left:2px}
|
|
73
|
+
.side-foot .icon-btn{margin-left:auto}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* TASK VIEW — the task page frame and its Overview tab: the sticky header and
|
|
3
|
+
* section tabs, the hero, the "now" banner, input requests that need the
|
|
4
|
+
* user, decisions and planning. Rows inside the other tabs live in
|
|
5
|
+
* views/records and views/delivery.
|
|
6
|
+
*/
|
|
7
|
+
export const TASK_STYLES = `
|
|
8
|
+
/* Sticky header and tab strip: both bleed through the page gutter and frost
|
|
9
|
+
the content scrolling beneath them. */
|
|
10
|
+
.task-header,.tabs-wrap{position:sticky;margin:0 calc(-1 * var(--gutter));padding:0 var(--gutter);background:color-mix(in srgb,var(--canvas) 88%,transparent);backdrop-filter:saturate(1.4) blur(10px);-webkit-backdrop-filter:saturate(1.4) blur(10px)}
|
|
11
|
+
.task-header{top:0;z-index:10}
|
|
12
|
+
.task-bar{display:flex;align-items:center;gap:6px;height:var(--header-h)}
|
|
13
|
+
.back-btn{display:none}
|
|
14
|
+
.crumbs{display:flex;align-items:center;gap:6px;min-width:0;font-size:var(--fs-sm)}
|
|
15
|
+
.crumb{padding:3px 6px;border:0;border-radius:var(--r-xs);background:transparent;color:var(--ink-3)}
|
|
16
|
+
.crumb:hover{color:var(--ink);background:var(--surface-3)}
|
|
17
|
+
.crumb-sep{color:var(--ink-4)}
|
|
18
|
+
.crumb-current{color:var(--ink-2);font-size:var(--fs-xs)}
|
|
19
|
+
.dock-toggle{height:30px}
|
|
20
|
+
.task-hero{padding:14px 0 18px}
|
|
21
|
+
.task-title{font-size:var(--fs-2xl);font-weight:650;letter-spacing:-.025em;line-height:1.25;overflow-wrap:anywhere}
|
|
22
|
+
.task-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:12px;color:var(--ink-3);font-size:var(--fs-xs)}
|
|
23
|
+
.task-meta:empty{display:none}
|
|
24
|
+
.meta-item{display:inline-flex;align-items:center;gap:5px}
|
|
25
|
+
.meta-item .when{font-size:inherit}
|
|
26
|
+
.tabs-wrap{top:var(--header-h);z-index:9;border-bottom:1px solid var(--line)}
|
|
27
|
+
.tabs{gap:4px}
|
|
28
|
+
.tab{position:relative;display:inline-flex;align-items:center;gap:7px;flex:none;height:42px;padding:0 10px;border:0;background:transparent;color:var(--ink-3);font-size:var(--fs-sm);font-weight:500}
|
|
29
|
+
.tab:hover{color:var(--ink)}
|
|
30
|
+
.tab[aria-selected="true"]{color:var(--ink)}
|
|
31
|
+
.tab[aria-selected="true"]::after{left:8px;right:8px;bottom:-1px}
|
|
32
|
+
.tab .icon{color:var(--ink-4)}
|
|
33
|
+
.tab[aria-selected="true"] .icon{color:var(--accent)}
|
|
34
|
+
.tab:focus-visible{box-shadow:inset var(--focus)}
|
|
35
|
+
.tab .count.tone-warn{background:var(--warn-soft);color:var(--warn)}
|
|
36
|
+
.tab-panel{display:flex;flex-direction:column;gap:16px;padding-top:20px;min-width:0}
|
|
37
|
+
.tab-panel [id^="detail-"]{scroll-margin-top:calc(var(--header-h) + 60px)}
|
|
38
|
+
|
|
39
|
+
/* Now banner: the execution status tone as a left stripe and a soft wash */
|
|
40
|
+
.now-card{border-color:var(--line-2);background:linear-gradient(100deg,var(--tone-soft,transparent),transparent 62%),var(--surface);box-shadow:inset 3px 0 0 var(--tone,var(--line-3))}
|
|
41
|
+
.now-card>.card-body{gap:0;padding:16px 18px 16px 20px}
|
|
42
|
+
.now-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
|
|
43
|
+
.now-owner{color:var(--ink-2);font-size:var(--fs-sm);font-weight:500}
|
|
44
|
+
.now-summary{margin-top:10px;font-size:var(--fs-lg);font-weight:500;line-height:1.45;color:var(--ink)}
|
|
45
|
+
.now-reason{margin-top:4px;color:var(--ink-3);font-size:var(--fs-sm)}
|
|
46
|
+
.now-note{margin-top:8px;color:var(--ink-3);font-size:var(--fs-sm)}
|
|
47
|
+
.signal-list{list-style:none;display:grid;gap:6px;margin:12px 0 0;padding:0}
|
|
48
|
+
.signal-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;padding:8px 10px;border-radius:var(--r-sm);background:var(--tone-soft);font-size:var(--fs-sm)}
|
|
49
|
+
.signal-row>.icon{align-self:center;color:var(--tone)}
|
|
50
|
+
.signal-kind{color:var(--tone);font-weight:600}
|
|
51
|
+
.next-line{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-top:12px;padding-top:10px;border-top:1px solid var(--line);color:var(--ink-3);font-size:var(--fs-xs)}
|
|
52
|
+
.next-line:empty{display:none}
|
|
53
|
+
.next-line .icon{color:var(--accent)}
|
|
54
|
+
.next-line strong{color:var(--ink)}
|
|
55
|
+
|
|
56
|
+
/* Needs you / input requests */
|
|
57
|
+
.needs-card{border-color:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}
|
|
58
|
+
.needs-card .card-icon{background:var(--warn-soft);color:var(--warn)}
|
|
59
|
+
.input-card{display:grid;gap:10px;padding:14px;border:1px solid var(--line-2);border-radius:var(--r-md);background:var(--surface-2)}
|
|
60
|
+
.input-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;color:var(--ink-3);font-size:var(--fs-xs)}
|
|
61
|
+
.input-kicker{display:inline-flex;align-items:center;gap:5px;color:var(--warn);font-weight:600;text-transform:uppercase;letter-spacing:.06em;font-size:var(--fs-2xs)}
|
|
62
|
+
.input-timeout{display:inline-flex;align-items:center;gap:4px}
|
|
63
|
+
.input-head .when{margin-left:auto}
|
|
64
|
+
.input-question{font-size:var(--fs-lg);font-weight:500;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
65
|
+
.input-facts{color:var(--ink-3);font-size:var(--fs-xs);overflow-wrap:anywhere}
|
|
66
|
+
.input-choices{display:flex;flex-wrap:wrap;gap:8px}
|
|
67
|
+
.choice{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:6px 14px;border:1px solid var(--line-3);border-radius:var(--r-md);background:var(--surface);color:var(--ink);font-weight:500;text-align:left}
|
|
68
|
+
.choice:hover:not(:disabled){border-color:var(--accent);background:var(--accent-soft)}
|
|
69
|
+
.choice.is-recommended{border-color:var(--accent-line)}
|
|
70
|
+
.choice:disabled{opacity:.5}
|
|
71
|
+
.input-form{display:flex;gap:8px}
|
|
72
|
+
.input-form input{flex:1}
|
|
73
|
+
|
|
74
|
+
/* Decisions, planning */
|
|
75
|
+
.decision{display:grid;gap:4px;padding-top:10px;border-top:1px solid var(--line)}
|
|
76
|
+
.decision:first-child{padding-top:0;border-top:0}
|
|
77
|
+
.decision-head{display:flex;align-items:center;gap:8px;font-size:var(--fs-sm)}
|
|
78
|
+
.decision-head strong{flex:1;min-width:0;font-weight:600}
|
|
79
|
+
.planning-card .kv{font-size:var(--fs-xs)}
|
|
80
|
+
`;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DEFAULT_DARK_THEME } from "../../shared/themes.js";
|
|
2
|
+
const SESSION_TONES = DEFAULT_DARK_THEME.tokens;
|
|
3
|
+
/*
|
|
4
|
+
* TERMINAL VIEW — the Live session pane. It renders a native terminal, so it
|
|
5
|
+
* stays dark in every theme: its chrome uses a scoped palette (white alphas
|
|
6
|
+
* and the default dark theme's status colours) instead of theme tokens.
|
|
7
|
+
*/
|
|
8
|
+
export const TERMINAL_STYLES = `
|
|
9
|
+
.dock-session{background:var(--term-bg);color:#e6e6e8;--chrome-line:rgba(255,255,255,.07);--chrome-ok:${SESSION_TONES.ok};--chrome-info:${SESSION_TONES.info};--chrome-bad:${SESSION_TONES.bad}}
|
|
10
|
+
.dock-session .faint{color:rgba(255,255,255,.4)}
|
|
11
|
+
|
|
12
|
+
/* Target bar: one chip per attachable Role, then the connection state */
|
|
13
|
+
.session-bar{flex:none;display:flex;align-items:center;gap:8px;min-height:44px;padding:6px 10px;border-bottom:1px solid var(--chrome-line)}
|
|
14
|
+
.session-targets{display:flex;gap:4px;flex:1;min-width:0;overflow-x:auto;scrollbar-width:none}
|
|
15
|
+
.target-chip{display:inline-flex;align-items:center;gap:6px;flex:none;height:26px;padding:0 10px;border:1px solid rgba(255,255,255,.12);border-radius:var(--r-pill);background:transparent;color:rgba(255,255,255,.72);font-size:var(--fs-xs);font-weight:500}
|
|
16
|
+
.target-chip:hover{color:#fff;border-color:rgba(255,255,255,.3)}
|
|
17
|
+
.target-chip[aria-pressed="true"]{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.3);color:#fff}
|
|
18
|
+
.target-chip .dot{width:7px;height:7px;box-shadow:none}
|
|
19
|
+
.conn{display:inline-flex;align-items:center;gap:6px;flex:none;color:rgba(255,255,255,.6);font-size:var(--fs-xs);font-weight:500}
|
|
20
|
+
.conn i{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.3)}
|
|
21
|
+
.conn[data-state="connecting"] i{background:var(--warn);animation:blink 1s infinite}
|
|
22
|
+
.conn[data-state="writable"] i{background:var(--chrome-ok);box-shadow:0 0 0 3px rgba(79,209,139,.2)}
|
|
23
|
+
.conn[data-state="readonly"] i{background:var(--chrome-info)}
|
|
24
|
+
.conn[data-state="error"] i{background:var(--chrome-bad)}
|
|
25
|
+
.conn[data-state="error"]{color:#ff9aa5}
|
|
26
|
+
|
|
27
|
+
/* Terminal host, its empty state and the CLI footer */
|
|
28
|
+
.terminal-host{flex:1;min-height:0;padding:10px 4px 6px 12px;overflow:hidden}
|
|
29
|
+
.terminal-host:empty{display:none}
|
|
30
|
+
.terminal-host .xterm{height:100%}
|
|
31
|
+
.terminal-host .xterm-viewport{background:transparent!important}
|
|
32
|
+
.terminal-empty{flex:1;display:grid;place-content:center;justify-items:center;gap:8px;padding:24px;text-align:center;color:rgba(255,255,255,.55)}
|
|
33
|
+
.terminal-empty .icon{color:rgba(255,255,255,.35)}
|
|
34
|
+
.terminal-empty-title{color:#fff;font-weight:600}
|
|
35
|
+
.terminal-empty-text{max-width:300px;font-size:var(--fs-xs);line-height:1.5}
|
|
36
|
+
.session-foot{flex:none;display:flex;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid var(--chrome-line);color:rgba(255,255,255,.45);font-size:var(--fs-2xs)}
|
|
37
|
+
.session-foot code{color:rgba(255,255,255,.8);user-select:all}
|
|
38
|
+
`;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { assertHttpPreviewSocket, isHttpPreview, validateHttpPreview } from "../job/httpPreview.js";
|
|
5
|
+
import { requestDurableJobCancel } from "../job/durableJob.js";
|
|
6
|
+
import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
|
|
7
|
+
const MAX_BYTES = 8 * 1024 * 1024;
|
|
8
|
+
const TIMEOUT_MS = 2000;
|
|
9
|
+
/** Only the authenticated Web composition root constructs this surface.
|
|
10
|
+
* No URL/host/port/cwd is accepted from the browser. Tickets authorize one
|
|
11
|
+
* live Job's GET/HEAD responses only and expire with this Web listener. */
|
|
12
|
+
export function createTaskPreviews(store, home) {
|
|
13
|
+
const secret = randomBytes(32);
|
|
14
|
+
const key = (taskId, jobId) => createHmac("sha256", secret).update(JSON.stringify([taskId, jobId])).digest("base64url");
|
|
15
|
+
const prefix = (job) => `/preview/${encodeURIComponent(job.taskId)}/${encodeURIComponent(job.id)}/${key(job.taskId, job.id)}/`;
|
|
16
|
+
const registered = (taskId, jobId) => {
|
|
17
|
+
const job = store.getDurableJob(taskId, jobId);
|
|
18
|
+
if (!store.getTask(taskId) || !job || job.taskId !== taskId || !isHttpPreview(job.env)) {
|
|
19
|
+
throw new WebRequestRejected("Registered Task preview not found.");
|
|
20
|
+
}
|
|
21
|
+
validateHttpPreview(job.env, job.steps);
|
|
22
|
+
return job;
|
|
23
|
+
};
|
|
24
|
+
const socket = (job) => {
|
|
25
|
+
// Derive from exact owned IDs, not a caller path or the displayed locator.
|
|
26
|
+
if (![job.taskId, job.id].every(id => /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(id))) {
|
|
27
|
+
throw new Error("Invalid preview owner.");
|
|
28
|
+
}
|
|
29
|
+
const dir = resolve(home, "artifacts", "jobs", job.taskId, job.id);
|
|
30
|
+
return assertHttpPreviewSocket(dir);
|
|
31
|
+
};
|
|
32
|
+
const state = (job) => {
|
|
33
|
+
if (job.status === "unknown-needs-attention")
|
|
34
|
+
return "unknown";
|
|
35
|
+
if (["cancelled", "succeeded"].includes(job.status))
|
|
36
|
+
return "stopped";
|
|
37
|
+
if (["failed", "timed-out"].includes(job.status))
|
|
38
|
+
return "failed";
|
|
39
|
+
if (job.cancelRequestedAt)
|
|
40
|
+
return "stopping";
|
|
41
|
+
const task = store.getTask(job.taskId);
|
|
42
|
+
if (task?.status !== "active" || task.executionGate.state !== "enabled")
|
|
43
|
+
return "unavailable";
|
|
44
|
+
return job.status === "queued" ? "starting" : "running";
|
|
45
|
+
};
|
|
46
|
+
const read = async (job, path, method) => {
|
|
47
|
+
if (state(registered(job.taskId, job.id)) !== "running")
|
|
48
|
+
throw new Error("Service is no longer available.");
|
|
49
|
+
const result = await readSocket(socket(job), path, method, prefix(job));
|
|
50
|
+
if (state(registered(job.taskId, job.id)) !== "running")
|
|
51
|
+
throw new Error("Service stopped during the request.");
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
async list(taskId) {
|
|
56
|
+
if (!store.getTask(taskId))
|
|
57
|
+
throw new WebRequestRejected("Task not found.");
|
|
58
|
+
const jobs = store.listDurableJobs(taskId).filter(job => isHttpPreview(job.env)).reverse();
|
|
59
|
+
const services = await Promise.all(jobs.slice(0, 16).map(async (job) => {
|
|
60
|
+
let current = state(job);
|
|
61
|
+
let detail = job.result?.unknownReason ?? `Job: ${job.status}`;
|
|
62
|
+
let status;
|
|
63
|
+
if (current === "running") {
|
|
64
|
+
try {
|
|
65
|
+
validateHttpPreview(job.env, job.steps);
|
|
66
|
+
const result = await read(job, "/", "GET");
|
|
67
|
+
status = result.status;
|
|
68
|
+
current = status >= 200 && status < 400 ? "ready" : "failed";
|
|
69
|
+
detail = `HTTP ${status} from the registered service.`;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const code = error.code;
|
|
73
|
+
const age = Date.now() - Date.parse(job.startedAt ?? job.createdAt);
|
|
74
|
+
current = (code === "ENOENT" || code === "ECONNREFUSED") && age < 30_000 ? "starting" : "failed";
|
|
75
|
+
detail = error instanceof Error ? error.message : "Service unavailable.";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const fresh = state(registered(taskId, job.id));
|
|
79
|
+
if (!["running", "starting"].includes(fresh))
|
|
80
|
+
current = fresh;
|
|
81
|
+
return {
|
|
82
|
+
id: job.id, name: job.steps[0]?.name ?? job.id, owner: job.owner,
|
|
83
|
+
state: current, detail, httpStatus: status, readAt: new Date().toISOString(),
|
|
84
|
+
...(current === "ready" ? { url: prefix(job) } : {}),
|
|
85
|
+
canStop: job.status === "queued" || job.status === "running",
|
|
86
|
+
receipt: job.result ? { outcome: job.result.outcome, terminalAt: job.terminalAt,
|
|
87
|
+
evidenceSource: job.result.evidenceSource, artifactsLocator: job.artifactsLocator } : null
|
|
88
|
+
};
|
|
89
|
+
}));
|
|
90
|
+
return { services, total: jobs.length, complete: jobs.length <= 16 };
|
|
91
|
+
},
|
|
92
|
+
stop(taskId, jobId) {
|
|
93
|
+
// HTTP authenticates the local human before entering this transaction.
|
|
94
|
+
// Reuse the same domain cancellation as CLI; never signal a browser PID.
|
|
95
|
+
return webLocalMutation(store, tx => {
|
|
96
|
+
const job = registered(taskId, jobId);
|
|
97
|
+
const next = requestDurableJobCancel(job, new Date());
|
|
98
|
+
if (next !== job)
|
|
99
|
+
tx.saveDurableJob(taskId, next);
|
|
100
|
+
return { jobId, status: next.status, cancelRequestedAt: next.cancelRequestedAt,
|
|
101
|
+
stopped: ["cancelled", "succeeded", "failed", "timed-out"].includes(next.status) };
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async serve(request, response) {
|
|
105
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
106
|
+
const match = /^\/preview\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+)\/(.*)$/.exec(url.pathname);
|
|
107
|
+
const fail = (status, detail) => {
|
|
108
|
+
response.statusCode = status;
|
|
109
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
110
|
+
response.end(detail);
|
|
111
|
+
};
|
|
112
|
+
if (!match) {
|
|
113
|
+
fail(403, "Invalid preview ticket.");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const [, taskId, jobId, ticket, path] = match;
|
|
117
|
+
const expected = key(taskId, jobId);
|
|
118
|
+
if (ticket.length !== expected.length || !timingSafeEqual(Buffer.from(ticket), Buffer.from(expected))) {
|
|
119
|
+
fail(403, "Invalid preview ticket.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
123
|
+
fail(405, "Preview supports GET and HEAD only.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let job;
|
|
127
|
+
try {
|
|
128
|
+
job = registered(taskId, jobId);
|
|
129
|
+
if (state(job) !== "running")
|
|
130
|
+
throw new Error("Service is not running; refresh Task preview status.");
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
fail(409, error.message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const result = await read(job, "/" + path + url.search, request.method);
|
|
138
|
+
const base = prefix(job);
|
|
139
|
+
// No upstream CSP, cookies, auth, redirects, or CORS escape this boundary.
|
|
140
|
+
const source = `http://${request.headers.host}${base}`;
|
|
141
|
+
response.setHeader("content-security-policy", [
|
|
142
|
+
"sandbox allow-scripts", "default-src 'none'",
|
|
143
|
+
`script-src 'unsafe-inline' ${source}`, `style-src 'unsafe-inline' ${source}`,
|
|
144
|
+
`img-src data: ${source}`, `font-src ${source}`, `connect-src ${source}`,
|
|
145
|
+
`base-uri ${source}`, "form-action 'none'", "frame-ancestors 'self'"
|
|
146
|
+
].join("; "));
|
|
147
|
+
response.removeHeader("x-frame-options");
|
|
148
|
+
response.setHeader("access-control-allow-origin", "null");
|
|
149
|
+
response.setHeader("cross-origin-resource-policy", "cross-origin");
|
|
150
|
+
response.setHeader("content-type", result.contentType);
|
|
151
|
+
if (result.location) {
|
|
152
|
+
// Only relative redirects are supported. A redirect can never select
|
|
153
|
+
// another upstream, even if it names localhost or this control plane.
|
|
154
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(result.location)) {
|
|
155
|
+
fail(502, "External/absolute redirects are not supported in preview.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const target = new URL(result.location, "http://preview.invalid/" + path + url.search);
|
|
159
|
+
response.setHeader("location", base + target.pathname.slice(1) + target.search + target.hash);
|
|
160
|
+
}
|
|
161
|
+
response.statusCode = result.status;
|
|
162
|
+
response.end(request.method === "HEAD" ? undefined : result.body);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
fail(502, `Preview unavailable: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function readSocket(socketPath, path, method, prefix) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const request = httpRequest({
|
|
173
|
+
socketPath, path, method, agent: false,
|
|
174
|
+
headers: { host: "preview.local", "accept-encoding": "identity", "x-forwarded-prefix": prefix }
|
|
175
|
+
});
|
|
176
|
+
// Absolute deadline also bounds a slowly streaming response.
|
|
177
|
+
const timer = setTimeout(() => request.destroy(new Error("Service did not respond within 2 seconds.")), TIMEOUT_MS);
|
|
178
|
+
request.once("error", reject);
|
|
179
|
+
request.once("close", () => clearTimeout(timer));
|
|
180
|
+
request.once("response", response => {
|
|
181
|
+
response.once("error", reject);
|
|
182
|
+
if (response.headers["content-encoding"] && response.headers["content-encoding"] !== "identity") {
|
|
183
|
+
response.resume();
|
|
184
|
+
request.destroy(new Error("Service must honor Accept-Encoding: identity for preview."));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const chunks = [];
|
|
188
|
+
let size = 0;
|
|
189
|
+
response.on("data", chunk => {
|
|
190
|
+
size += chunk.length;
|
|
191
|
+
if (size > MAX_BYTES)
|
|
192
|
+
request.destroy(new Error("Preview response exceeds 8 MiB."));
|
|
193
|
+
else
|
|
194
|
+
chunks.push(chunk);
|
|
195
|
+
});
|
|
196
|
+
response.once("end", () => resolve({
|
|
197
|
+
status: response.statusCode ?? 502,
|
|
198
|
+
contentType: response.headers["content-type"] ?? "application/octet-stream",
|
|
199
|
+
...(response.headers.location ? { location: response.headers.location } : {}),
|
|
200
|
+
body: Buffer.concat(chunks)
|
|
201
|
+
}));
|
|
202
|
+
});
|
|
203
|
+
request.end();
|
|
204
|
+
});
|
|
205
|
+
}
|