@qilin-agent/client-ui-jobs 0.0.0-stage → 3.1.3
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/LICENSE +21 -0
- package/README.i18n.yaml +40 -0
- package/README.md +86 -2
- package/README.zh.md +87 -0
- package/lib/client.js +628 -0
- package/lib/index.js +11 -0
- package/lib/types/client/JobListAction.d.ts +46 -0
- package/lib/types/client/index.d.ts +23 -0
- package/lib/types/client/locales.d.ts +50 -0
- package/lib/types/index.d.ts +9 -0
- package/package.json +67 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-jobs/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 5c00e054d1e02700
|
|
7
|
+
zh: 678f43f0276422f8
|
|
8
|
+
/qilin-client-ui-jobs:
|
|
9
|
+
en: a0116b43189cb96c
|
|
10
|
+
zh: cae51d35bf4c13db
|
|
11
|
+
/qilin-client-ui-jobs/summary:
|
|
12
|
+
en: fac5c2092b57d9df
|
|
13
|
+
zh: ac05650a6f9dc77a
|
|
14
|
+
/qilin-client-ui-jobs/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/qilin-client-ui-jobs/use-this-package:
|
|
18
|
+
en: 32646e32b47172e7
|
|
19
|
+
zh: 83e8fff6de2e8b05
|
|
20
|
+
/qilin-client-ui-jobs/use-this-package/dismissal-and-limits:
|
|
21
|
+
en: 63efd5a2b0986bfb
|
|
22
|
+
zh: ecfc938bdd5c83c6
|
|
23
|
+
/qilin-client-ui-jobs/understand-the-implementation:
|
|
24
|
+
en: cee083b101337f44
|
|
25
|
+
zh: 38e5c4a8278d339b
|
|
26
|
+
/qilin-client-ui-jobs/further-exploration:
|
|
27
|
+
en: c58e82a3ce217c32
|
|
28
|
+
zh: d6bd297d31bc3c4e
|
|
29
|
+
/qilin-client-ui-jobs/model-experience:
|
|
30
|
+
en: f9ec590439742792
|
|
31
|
+
zh: 5914634c4b1c0912
|
|
32
|
+
/qilin-client-ui-jobs/model-experience/kv-cache-effect:
|
|
33
|
+
en: 47f982e326fe77fd
|
|
34
|
+
zh: c2241315dc0c89c9
|
|
35
|
+
/qilin-client-ui-jobs/known-limitations-and-deferred-work:
|
|
36
|
+
en: 82b0b7f1c1dcde46
|
|
37
|
+
zh: 03344ce9b638799f
|
|
38
|
+
/qilin-client-ui-jobs/known-limitations-and-deferred-work/dev-note:
|
|
39
|
+
en: e67f633dc6fe7773
|
|
40
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Web background-job surface: the session-header action listing the jobs this session can see; for users and maintainers of the background-job experience."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/client-ui-jobs
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
This package renders the background-job surface of the Web GUI: a session-header action that opens a popover listing the jobs this session can see. It reads host-computed registry state through the runtime's `jobsBySession` mirror and issues no RPC of its own. The trigger appears only when the session has at least one job, with a badge counting running and stopping jobs; settled rows stay visible and de-emphasized until the registry drops them. The model's own view of the same jobs belongs to `qilin-tool-jobs`; this package is a read-only projection for the human.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount this plugin alongside the runtime; the job action then appears in the session header whenever the session has at least one job. A click opens the popover: live rows first by start time, then settled rows by finish time, each showing the producer kind, label, status, and an elapsed duration that ticks once per second while live and freezes at completion.
|
|
29
|
+
|
|
30
|
+
### Dismissal and limits
|
|
31
|
+
|
|
32
|
+
Escape closes the list and returns focus to the trigger, as does a pointer press outside it. The list shows what one session can see through the wire view, so a job owned by another session never appears here; a process restart empties the list while the transcript keeps the `run_in_background` cards that started those jobs.
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## Understand the implementation
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary>Implementation internals — click to expand</summary>
|
|
41
|
+
|
|
42
|
+
The package contributes one entry to `conversation.session.header.actions` (`JobListAction`), and the data arrives entirely through the `jobsBySession` list mirror that the Session Controller binding folds from `session/jobs` frames — no RPC, and no state beyond popover visibility. The badge counts `running` plus `stopping` and is omitted at zero. Rows are ordered with live rows first by `startedAt` ascending, then settled rows by `finishedAt` descending, with a same-millisecond tie broken on start order; a settled row missing `finishedAt` reads as zero rather than as a negative figure, and a duration past an hour stays in hours. Settled rows stay visible because a failed job's `detail` is the only place its failure is legible. The behavior is specified by the [Web background-job display Agent Note](../../../.agents/notes/implemented/feature/2026-08-08-web-background-job-display.md).
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## Further Exploration
|
|
50
|
+
|
|
51
|
+
Read these pages when the job surface is not enough. They move from the browser list to the registry and the model-facing tool.
|
|
52
|
+
|
|
53
|
+
- [qilin-tool-jobs](../../jobs/tool-jobs/README.md) — the model-facing jobs tool over the same registry.
|
|
54
|
+
- [Session Controller](../../api/session-controller/README.md) — folds the `jobsBySession` mirror this package reads.
|
|
55
|
+
- [ui-subagent](../ui-subagent/README.md) — the subagent catalog, where a running one-shot background subagent also appears.
|
|
56
|
+
- [Web client architecture](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) — how browser plugin rows load and register slots.
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="model-experience"></a>
|
|
61
|
+
## Model Experience
|
|
62
|
+
|
|
63
|
+
None, as this package renders host-computed registry state for a human and touches no prompt, message, schema, stream, or tool result.
|
|
64
|
+
|
|
65
|
+
#### KV Cache effect
|
|
66
|
+
|
|
67
|
+
None; the package never assembles or sends provider requests.
|
|
68
|
+
|
|
69
|
+
## Known Limitations and Deferred Work
|
|
70
|
+
|
|
71
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
These limits define the current job list. They are current package constraints, not a general job-management comparison or a task backlog.
|
|
75
|
+
|
|
76
|
+
- **Rows are read-only** — a job's streamed output and a human-initiated cancellation are separate phases. Cancellation additionally owes a model-facing decision the seam does not answer: `kill()` marks terminal delivery reported, so an interrupt written against the current contract would leave the model believing its job is still running.
|
|
77
|
+
- **The list is not the registry's own set** — it shows what one session can see through the wire view, so a job owned by another session never appears here, and a process restart empties the list while the transcript keeps the `run_in_background` cards that started those jobs. An unowned job (one started without a live `Agent`) reaches every session's list, matching what `list(caller)` reports to every caller.
|
|
78
|
+
|
|
79
|
+
<a id="dev-note"></a>
|
|
80
|
+
### Dev Note
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
84
|
+
|
|
85
|
+
None.
|
|
86
|
+
|
|
87
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Web 后台任务界面:列出本会话可见任务的会话头部动作;供后台任务体验的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/client-ui-jobs
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
本包渲染 Web GUI 的后台任务界面:一个会话头部动作,打开后以弹层列出本会话可见的任务。它经运行时提供的 `jobsBySession` 镜像读取宿主计算的注册表状态,自身不发任何 RPC。触发器只在会话至少有一个任务时出现,角标计数运行中与停止中的任务;终态行保持可见并弱化,直到注册表把它们丢弃。模型对同一批任务的视角属于 `qilin-tool-jobs`;本包是给人类看的只读投影。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
与运行时一起挂载本插件;只要会话至少有一个任务,任务动作就会出现在会话头部。点击打开弹层:活跃行在前按开始时间升序,随后终态行按结束时间降序,每行显示生产者 kind、标签、状态,以及一个活跃时每秒跳动、完成后冻结的已耗时。
|
|
29
|
+
|
|
30
|
+
### 关闭与边界
|
|
31
|
+
|
|
32
|
+
Escape 关闭列表并把焦点交还触发器,在其外部按下指针同理。列表展示的是「一个会话通过协议视图能看到什么」,因此别的会话拥有的任务在这里永不出现;进程重启会清空列表,而 transcript(文本记录)里启动这些任务的 `run_in_background` 卡片仍在。
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## 理解实现
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary>实现细节——点击展开</summary>
|
|
41
|
+
|
|
42
|
+
本包向 `conversation.session.header.actions` 贡献一个条目(`JobListAction`),数据完全来自会话控制器绑定从 `session/jobs` 帧折叠出的 `jobsBySession` 列表镜像——不发 RPC,除弹层开合外不持有任何状态。角标计数 `running` 加 `stopping`,为零时省略。行序为活跃行在前按 `startedAt` 升序、终态行按 `finishedAt` 降序,毫秒并列按启动顺序打破;缺少 `finishedAt` 的终态行读作零而不是负数,超过一小时的耗时停留在小时单位。终态行保持可见,因为失败任务的 `detail` 是其失败唯一可读之处。行为由 [Web 后台任务展示 Agent Note](../../../.agents/notes/implemented/feature/2026-08-08-web-background-job-display.zh.md) 规定。
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## 进一步探索
|
|
50
|
+
|
|
51
|
+
当任务界面本身无法满足需要时,请阅读以下页面。它们从浏览器列表进入注册表与面向模型的工具。
|
|
52
|
+
|
|
53
|
+
- [qilin-tool-jobs](../../jobs/tool-jobs/README.zh.md)——同一注册表之上的面向模型任务工具。
|
|
54
|
+
- [会话控制器](../../api/session-controller/README.zh.md)——折叠出本包读取的 `jobsBySession` 镜像。
|
|
55
|
+
- [ui-subagent](../ui-subagent/README.zh.md)——subagent 目录,运行中的一次性后台 subagent 也会出现在那里。
|
|
56
|
+
- [Web 客户端架构](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md)——浏览器插件行如何加载并注册 slot。
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="model-experience"></a>
|
|
61
|
+
## 模型体验
|
|
62
|
+
|
|
63
|
+
无,因为本包为人类渲染宿主计算出的注册表状态,不触及提示词、消息、schema、流或工具结果。
|
|
64
|
+
|
|
65
|
+
#### KV Cache 影响
|
|
66
|
+
|
|
67
|
+
无;本包从不组装或发送提供方请求。
|
|
68
|
+
|
|
69
|
+
## 已知限制与延期工作
|
|
70
|
+
|
|
71
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
这些限制界定了当前任务列表。它们是当前包约束,不是通用任务管理对比或任务积压。
|
|
75
|
+
|
|
76
|
+
- **行是只读的**——任务的流式输出与人类发起的取消是各自独立的阶段。取消还额外欠一个 seam 没有回答的、面向模型的决策:`kill()` 会把终态投递标为已上报,所以照当前契约写出来的中断会让模型一直以为它的任务还在跑。
|
|
77
|
+
- **列表不等于注册表自己的集合**——它展示的是一个会话通过协议视图能看到什么,因此别的会话拥有的任务在这里永不出现;进程重启会清空列表,而 transcript 里启动这些任务的 `run_in_background` 卡片仍在。无主任务(没有活体 `Agent` 时启动的)反过来会进入每个会话的列表,与 `list(caller)` 对每个调用方的报告一致。
|
|
78
|
+
|
|
79
|
+
<a id="dev-note"></a>
|
|
80
|
+
### 开发备注
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
84
|
+
|
|
85
|
+
无。
|
|
86
|
+
|
|
87
|
+
</details>
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-jobs",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
10
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-jobs/src/client/JobListAction.module.css.mjs
|
|
11
|
+
const css = ".IxCCqa_root{position:relative}.IxCCqa_trigger{border-radius:var(--qilin-radius-sm);min-height:28px;color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;align-items:center;gap:3px;padding:3px 2px;font-size:12px;line-height:18px;display:inline-flex}.IxCCqa_trigger:hover,.IxCCqa_trigger:focus-visible{color:var(--qilin-alias-label-secondary)}.IxCCqa_trigger svg{transition:transform .12s}.IxCCqa_triggerOpen{transform:rotate(180deg)}.IxCCqa_triggerDot{flex:none}.IxCCqa_count{margin:0 5px}.IxCCqa_menu{z-index:100;box-sizing:border-box;border-radius:var(--qilin-radius-lg);background:var(--qilin-specific-menu);width:500px;max-width:min(560px,100vw - 32px);max-height:min(480px,100vh - 140px);backdrop-filter:var(--qilin-menu-backdrop-filter);--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);--qilin-elevation-stroke-color:var(--qilin-alias-border-l1);box-shadow:var(--qilin-elevation-prominent);border:0;flex-direction:column;gap:1px;margin:0;padding:3px;list-style:none;display:flex;position:absolute;top:calc(100% + 5px);left:0;overflow:auto}.IxCCqa_sectionHeader{border-top:.5px solid var(--qilin-alias-border-l1);color:var(--qilin-alias-label-tertiary);justify-content:space-between;align-items:center;margin:4px 4px 1px;padding:4px 0 3px;font-size:11px;line-height:16px;display:flex}.IxCCqa_sectionHeader:first-child{border-top:0;margin-top:0}.IxCCqa_sectionToggle,.IxCCqa_sectionClear{border-radius:var(--qilin-radius-sm);color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;align-items:center;gap:3px;padding:2px 4px;font-size:11px;line-height:16px;display:inline-flex}.IxCCqa_sectionToggle:hover,.IxCCqa_sectionToggle:focus-visible,.IxCCqa_sectionClear:hover,.IxCCqa_sectionClear:focus-visible{background:var(--qilin-alias-fill-l1);color:var(--qilin-alias-label-secondary)}.IxCCqa_sectionChevron{transition:transform .12s;transform:rotate(-90deg)}.IxCCqa_sectionChevronOpen{transform:none}.IxCCqa_item{flex-direction:column;display:flex}.IxCCqa_rowLine{align-items:stretch;gap:6px;display:flex}.IxCCqa_rowLineLive{border-radius:var(--qilin-radius-md);background:var(--qilin-alias-fill-l2);padding:4px 8px 4px 2px}.IxCCqa_rowLineLive:hover{background:color-mix(in srgb, var(--qilin-alias-label-primary) 9%, transparent)}.IxCCqa_rowLineLive .IxCCqa_kind{background:var(--qilin-specific-menu)}.IxCCqa_rowLine .IxCCqa_row{flex:1;min-width:0}.IxCCqa_rowLineLive .IxCCqa_row:hover{background:0 0}.IxCCqa_rowLineLive .IxCCqa_row:focus-visible{background:color-mix(in srgb, var(--qilin-alias-label-primary) 6%, transparent)}.IxCCqa_chevronBox{border:.5px solid var(--qilin-alias-border-l2);border-radius:var(--qilin-radius-sm);background:var(--qilin-specific-menu);flex:none;justify-content:center;align-self:center;align-items:center;width:20px;height:20px;display:inline-flex}.IxCCqa_stop{border:.5px solid var(--qilin-alias-border-l2);border-radius:var(--qilin-radius-sm);background:var(--qilin-specific-menu);width:20px;height:20px;color:var(--qilin-alias-label-secondary);cursor:pointer;flex:none;justify-content:center;align-self:center;align-items:center;padding:0;display:inline-flex}.IxCCqa_stop:hover,.IxCCqa_stop:focus-visible{border-color:color-mix(in srgb, var(--qilin-alias-state-error-primary) 45%, transparent);color:var(--qilin-alias-state-error-primary)}.IxCCqa_stopArmed,.IxCCqa_stopArmed:hover,.IxCCqa_stopArmed:focus-visible{border-color:color-mix(in srgb, var(--qilin-alias-state-error-primary) 45%, transparent);background:color-mix(in srgb, var(--qilin-alias-state-error-primary) 12%, transparent);width:auto;color:var(--qilin-alias-state-error-primary);gap:4px;padding:0 7px}.IxCCqa_stopLabel{white-space:nowrap;font-size:11px;line-height:18px}.IxCCqa_stopFailed{color:var(--qilin-alias-state-error-primary)}.IxCCqa_row{box-sizing:border-box;border-radius:var(--qilin-radius-sm);width:100%;min-height:28px;color:var(--qilin-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:4px 7px;font-size:12px;line-height:17px;display:flex}.IxCCqa_row:hover,.IxCCqa_row:focus-visible{background:var(--qilin-alias-fill-l1)}.IxCCqa_rowStatic{cursor:default}.IxCCqa_rowStatic:hover{background:0 0}.IxCCqa_rowSettled{color:var(--qilin-alias-label-tertiary)}.IxCCqa_row .IxCCqa_rowDot{flex:none}.IxCCqa_main{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.IxCCqa_primary{align-items:baseline;gap:8px;min-width:0;display:flex}.IxCCqa_secondary{align-items:center;gap:6px;min-width:0;display:flex}.IxCCqa_kind{border-radius:var(--qilin-radius-xs);background:var(--qilin-alias-fill-l2);color:var(--qilin-alias-label-secondary);flex:none;padding:0 5px;font-size:10px;line-height:16px}.IxCCqa_label{min-width:0;font-family:var(--qilin-font-mono);white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.IxCCqa_status{max-width:60%;color:var(--qilin-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:10px;line-height:16px;overflow:hidden}.IxCCqa_duration{color:var(--qilin-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:11px;line-height:18px}.IxCCqa_chevron{color:var(--qilin-alias-label-tertiary);flex:none}.IxCCqa_chevronOpen{transform:rotate(180deg)}.IxCCqa_panel{--dsl-terminal-command-whitespace:pre-wrap;--dsl-terminal-line-whitespace:pre-wrap;--dsl-terminal-output-max-height:288px;margin:0 8px 8px}.IxCCqa_panel [data-terminal]{--dsl-terminal-gutter:12px}.IxCCqa_notice{color:var(--qilin-alias-label-tertiary);margin:0 8px 6px;font-size:11px;line-height:16px}.IxCCqa_noticeError{color:var(--qilin-alias-state-error-primary)}";
|
|
12
|
+
const tagId = "@qilin-agent/client-ui-jobs/JobListAction.module.css";
|
|
13
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
14
|
+
const tag = document.createElement("style");
|
|
15
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-jobs";
|
|
16
|
+
tag.dataset.pluginCss = tagId;
|
|
17
|
+
tag.textContent = css;
|
|
18
|
+
document.head.appendChild(tag);
|
|
19
|
+
}
|
|
20
|
+
var JobListAction_module_css_default = {
|
|
21
|
+
"chevron": "IxCCqa_chevron",
|
|
22
|
+
"chevronBox": "IxCCqa_chevronBox",
|
|
23
|
+
"chevronOpen": "IxCCqa_chevronOpen",
|
|
24
|
+
"count": "IxCCqa_count",
|
|
25
|
+
"duration": "IxCCqa_duration",
|
|
26
|
+
"item": "IxCCqa_item",
|
|
27
|
+
"kind": "IxCCqa_kind",
|
|
28
|
+
"label": "IxCCqa_label",
|
|
29
|
+
"main": "IxCCqa_main",
|
|
30
|
+
"menu": "IxCCqa_menu",
|
|
31
|
+
"notice": "IxCCqa_notice",
|
|
32
|
+
"noticeError": "IxCCqa_noticeError",
|
|
33
|
+
"panel": "IxCCqa_panel",
|
|
34
|
+
"primary": "IxCCqa_primary",
|
|
35
|
+
"root": "IxCCqa_root",
|
|
36
|
+
"row": "IxCCqa_row",
|
|
37
|
+
"rowDot": "IxCCqa_rowDot",
|
|
38
|
+
"rowLine": "IxCCqa_rowLine",
|
|
39
|
+
"rowLineLive": "IxCCqa_rowLineLive",
|
|
40
|
+
"rowSettled": "IxCCqa_rowSettled",
|
|
41
|
+
"rowStatic": "IxCCqa_rowStatic",
|
|
42
|
+
"secondary": "IxCCqa_secondary",
|
|
43
|
+
"sectionChevron": "IxCCqa_sectionChevron",
|
|
44
|
+
"sectionChevronOpen": "IxCCqa_sectionChevronOpen",
|
|
45
|
+
"sectionClear": "IxCCqa_sectionClear",
|
|
46
|
+
"sectionHeader": "IxCCqa_sectionHeader",
|
|
47
|
+
"sectionToggle": "IxCCqa_sectionToggle",
|
|
48
|
+
"status": "IxCCqa_status",
|
|
49
|
+
"stop": "IxCCqa_stop",
|
|
50
|
+
"stopArmed": "IxCCqa_stopArmed",
|
|
51
|
+
"stopFailed": "IxCCqa_stopFailed",
|
|
52
|
+
"stopLabel": "IxCCqa_stopLabel",
|
|
53
|
+
"trigger": "IxCCqa_trigger",
|
|
54
|
+
"triggerDot": "IxCCqa_triggerDot",
|
|
55
|
+
"triggerOpen": "IxCCqa_triggerOpen"
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region lib/types/client/JobListAction.js
|
|
59
|
+
/** Stable empty list so a session with no jobs keeps one array identity. */
|
|
60
|
+
const NO_JOBS = [];
|
|
61
|
+
/** Minimum gap kept between the popover and the viewport edges (the Menu primitive's portal margin). */
|
|
62
|
+
const VIEWPORT_MARGIN = 12;
|
|
63
|
+
/** How long an armed kill waits for its confirming press before disarming. */
|
|
64
|
+
const KILL_ARM_MS = 3e3;
|
|
65
|
+
/** How long a failed kill keeps its hint before the button resets. */
|
|
66
|
+
const KILL_FAILED_MS = 4e3;
|
|
67
|
+
function isLive(job) {
|
|
68
|
+
return job.status === "running" || job.status === "stopping";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether the row offers an output panel: every live job (its output may
|
|
72
|
+
* still arrive) and a settled one that left retained output behind.
|
|
73
|
+
*/
|
|
74
|
+
function isObservable(job) {
|
|
75
|
+
return isLive(job) || job.output.total > 0;
|
|
76
|
+
}
|
|
77
|
+
/** The one-line qualifier beside the status: live progress while running, the terminal reason once settled. */
|
|
78
|
+
function jobDetail(job) {
|
|
79
|
+
return job.progress ?? job.detail;
|
|
80
|
+
}
|
|
81
|
+
/** Closed-union exhaustiveness fence for the wire status set. */
|
|
82
|
+
/* v8 ignore next 3 -- closed-union backstop; only reached if a status is forged */
|
|
83
|
+
function assertNever(value) {
|
|
84
|
+
throw new Error(`unhandled job status: ${JSON.stringify(value)}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Status marker semantics. `stopping` and `killed` share the attention color:
|
|
88
|
+
* both mean the work ended (or is ending) on request rather than on its own.
|
|
89
|
+
*/
|
|
90
|
+
function dotState(status) {
|
|
91
|
+
switch (status) {
|
|
92
|
+
case "running": return "ongoing";
|
|
93
|
+
case "stopping": return "warning";
|
|
94
|
+
case "completed": return "done";
|
|
95
|
+
case "killed": return "warning";
|
|
96
|
+
case "failed": return "error";
|
|
97
|
+
/* v8 ignore next -- closed wire status union */
|
|
98
|
+
default: return assertNever(status);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function statusLabel(status, t) {
|
|
102
|
+
switch (status) {
|
|
103
|
+
case "running": return t("status.running");
|
|
104
|
+
case "stopping": return t("status.stopping");
|
|
105
|
+
case "completed": return t("status.completed");
|
|
106
|
+
case "killed": return t("status.killed");
|
|
107
|
+
case "failed": return t("status.failed");
|
|
108
|
+
/* v8 ignore next -- closed wire status union */
|
|
109
|
+
default: return assertNever(status);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Elapsed time in at most two adjacent units. A job that outlives an hour is
|
|
114
|
+
* already exceptional, so hours is the widest unit — beyond that the figure
|
|
115
|
+
* stays in hours rather than growing a day/month vocabulary no producer
|
|
116
|
+
* currently reaches.
|
|
117
|
+
*/
|
|
118
|
+
function formatDuration(elapsedMs, t) {
|
|
119
|
+
const total = Math.max(0, Math.floor(elapsedMs / 1e3));
|
|
120
|
+
const seconds = total % 60;
|
|
121
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
122
|
+
const hours = Math.floor(total / 3600);
|
|
123
|
+
if (hours > 0) return t("duration.hours", {
|
|
124
|
+
hours,
|
|
125
|
+
minutes
|
|
126
|
+
});
|
|
127
|
+
if (minutes > 0) return t("duration.minutes", {
|
|
128
|
+
minutes,
|
|
129
|
+
seconds
|
|
130
|
+
});
|
|
131
|
+
return t("duration.seconds", { seconds });
|
|
132
|
+
}
|
|
133
|
+
/** Localized display copy for the embedded terminal panel. */
|
|
134
|
+
function terminalLabels(t) {
|
|
135
|
+
return {
|
|
136
|
+
/* v8 ignore next */
|
|
137
|
+
signal: (signal) => t("terminal.signal", { signal }),
|
|
138
|
+
/* v8 ignore next */
|
|
139
|
+
exitCode: (code) => t("terminal.exitCode", { code }),
|
|
140
|
+
noExitCode: t("terminal.noExitCode"),
|
|
141
|
+
running: t("terminal.running"),
|
|
142
|
+
failed: t("terminal.failed"),
|
|
143
|
+
done: t("terminal.done"),
|
|
144
|
+
copy: t("terminal.copy"),
|
|
145
|
+
copied: t("terminal.copied"),
|
|
146
|
+
noOutput: t("terminal.noOutput"),
|
|
147
|
+
collapseAria: t("terminal.collapseAria"),
|
|
148
|
+
collapse: t("terminal.collapse"),
|
|
149
|
+
/* v8 ignore next */
|
|
150
|
+
expandAria: (hidden) => t("terminal.expandAria", { n: hidden }),
|
|
151
|
+
/* v8 ignore next */
|
|
152
|
+
expand: (hidden) => t("terminal.expand", { n: hidden })
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Live rows first in start order, then settled rows newest-first. Two rows
|
|
157
|
+
* that settled in the same millisecond fall back to start order, so the sort
|
|
158
|
+
* never depends on the host's map iteration.
|
|
159
|
+
*/
|
|
160
|
+
function ordered(jobs) {
|
|
161
|
+
return [...jobs].sort((left, right) => {
|
|
162
|
+
const liveLeft = isLive(left);
|
|
163
|
+
if (liveLeft !== isLive(right)) return liveLeft ? -1 : 1;
|
|
164
|
+
if (liveLeft) return left.startedAt - right.startedAt;
|
|
165
|
+
const finished = (right.finishedAt ?? right.startedAt) - (left.finishedAt ?? left.startedAt);
|
|
166
|
+
return finished !== 0 ? finished : left.startedAt - right.startedAt;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/** One job row plus, when observable and expanded, its live output panel. */
|
|
170
|
+
function JobItem({ job, view, expanded, now, onToggle, kill, t }) {
|
|
171
|
+
const live = isLive(job);
|
|
172
|
+
const status = statusLabel(job.status, t);
|
|
173
|
+
const detail = jobDetail(job);
|
|
174
|
+
const observable = isObservable(job);
|
|
175
|
+
const labels = (0, react.useMemo)(() => terminalLabels(t), [t]);
|
|
176
|
+
const duration = formatDuration(live ? now - job.startedAt : (job.finishedAt ?? job.startedAt) - job.startedAt, t);
|
|
177
|
+
const durationCell = (0, react_jsx_runtime.jsx)("span", {
|
|
178
|
+
className: JobListAction_module_css_default.duration,
|
|
179
|
+
title: t(live ? "duration.title.live" : "duration.title.done", { duration }),
|
|
180
|
+
children: duration
|
|
181
|
+
});
|
|
182
|
+
const body = live ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
183
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.StateDot, {
|
|
184
|
+
state: dotState(job.status),
|
|
185
|
+
className: JobListAction_module_css_default.rowDot
|
|
186
|
+
}),
|
|
187
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
188
|
+
className: JobListAction_module_css_default.main,
|
|
189
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
190
|
+
className: JobListAction_module_css_default.primary,
|
|
191
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
192
|
+
className: JobListAction_module_css_default.label,
|
|
193
|
+
title: job.label,
|
|
194
|
+
children: job.label
|
|
195
|
+
})
|
|
196
|
+
}), (0, react_jsx_runtime.jsxs)("span", {
|
|
197
|
+
className: JobListAction_module_css_default.secondary,
|
|
198
|
+
title: detail ?? status,
|
|
199
|
+
children: [
|
|
200
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
201
|
+
className: JobListAction_module_css_default.kind,
|
|
202
|
+
children: job.kind
|
|
203
|
+
}),
|
|
204
|
+
detail !== void 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
205
|
+
className: JobListAction_module_css_default.status,
|
|
206
|
+
children: detail
|
|
207
|
+
}) : null,
|
|
208
|
+
durationCell
|
|
209
|
+
]
|
|
210
|
+
})]
|
|
211
|
+
}),
|
|
212
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
213
|
+
className: JobListAction_module_css_default.chevronBox,
|
|
214
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, {
|
|
215
|
+
size: 12,
|
|
216
|
+
className: expanded ? `${JobListAction_module_css_default.chevron} ${JobListAction_module_css_default.chevronOpen}` : JobListAction_module_css_default.chevron
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
220
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.StateDot, {
|
|
221
|
+
state: dotState(job.status),
|
|
222
|
+
className: JobListAction_module_css_default.rowDot
|
|
223
|
+
}),
|
|
224
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
225
|
+
className: JobListAction_module_css_default.kind,
|
|
226
|
+
children: job.kind
|
|
227
|
+
}),
|
|
228
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
229
|
+
className: JobListAction_module_css_default.label,
|
|
230
|
+
title: job.label,
|
|
231
|
+
children: job.label
|
|
232
|
+
}),
|
|
233
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
234
|
+
className: JobListAction_module_css_default.status,
|
|
235
|
+
title: detail ?? status,
|
|
236
|
+
children: detail ?? status
|
|
237
|
+
}),
|
|
238
|
+
durationCell,
|
|
239
|
+
observable ? (0, react_jsx_runtime.jsx)("span", {
|
|
240
|
+
className: JobListAction_module_css_default.chevronBox,
|
|
241
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, {
|
|
242
|
+
size: 12,
|
|
243
|
+
className: expanded ? `${JobListAction_module_css_default.chevron} ${JobListAction_module_css_default.chevronOpen}` : JobListAction_module_css_default.chevron
|
|
244
|
+
})
|
|
245
|
+
}) : null
|
|
246
|
+
] });
|
|
247
|
+
const killTitle = kill === void 0 ? void 0 : kill.state === "armed" ? t("kill.confirm") : kill.state === "failed" ? t("kill.failed") : t("kill.stop", { label: job.label });
|
|
248
|
+
return (0, react_jsx_runtime.jsxs)("li", {
|
|
249
|
+
className: JobListAction_module_css_default.item,
|
|
250
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
251
|
+
className: live ? `${JobListAction_module_css_default.rowLine} ${JobListAction_module_css_default.rowLineLive}` : JobListAction_module_css_default.rowLine,
|
|
252
|
+
children: [observable ? (0, react_jsx_runtime.jsx)("button", {
|
|
253
|
+
type: "button",
|
|
254
|
+
className: live ? JobListAction_module_css_default.row : `${JobListAction_module_css_default.row} ${JobListAction_module_css_default.rowSettled}`,
|
|
255
|
+
"aria-expanded": expanded,
|
|
256
|
+
"aria-label": t(expanded ? "row.collapseAria" : "row.expandAria", { label: job.label }),
|
|
257
|
+
onClick: onToggle,
|
|
258
|
+
children: body
|
|
259
|
+
}) : (0, react_jsx_runtime.jsx)("span", {
|
|
260
|
+
className: `${JobListAction_module_css_default.row} ${JobListAction_module_css_default.rowSettled} ${JobListAction_module_css_default.rowStatic}`,
|
|
261
|
+
children: body
|
|
262
|
+
}), kill !== void 0 ? (0, react_jsx_runtime.jsxs)("button", {
|
|
263
|
+
type: "button",
|
|
264
|
+
className: kill.state === "armed" ? `${JobListAction_module_css_default.stop} ${JobListAction_module_css_default.stopArmed}` : kill.state === "failed" ? `${JobListAction_module_css_default.stop} ${JobListAction_module_css_default.stopFailed}` : JobListAction_module_css_default.stop,
|
|
265
|
+
"data-kill-state": kill.state,
|
|
266
|
+
disabled: kill.state === "pending",
|
|
267
|
+
"aria-label": killTitle,
|
|
268
|
+
title: killTitle,
|
|
269
|
+
onClick: kill.onPress,
|
|
270
|
+
children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconStopFill16, { size: 10 }), kill.state === "armed" ? (0, react_jsx_runtime.jsx)("span", {
|
|
271
|
+
className: JobListAction_module_css_default.stopLabel,
|
|
272
|
+
children: t("kill.confirmAction")
|
|
273
|
+
}) : null]
|
|
274
|
+
}) : null]
|
|
275
|
+
}), expanded && view !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
276
|
+
className: JobListAction_module_css_default.panel,
|
|
277
|
+
children: [
|
|
278
|
+
view.gapBefore ? (0, react_jsx_runtime.jsx)("div", {
|
|
279
|
+
className: JobListAction_module_css_default.notice,
|
|
280
|
+
children: t("output.gap")
|
|
281
|
+
}) : null,
|
|
282
|
+
view.error !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
283
|
+
className: `${JobListAction_module_css_default.notice} ${JobListAction_module_css_default.noticeError}`,
|
|
284
|
+
children: t("output.error", { error: view.error })
|
|
285
|
+
}) : null,
|
|
286
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.TerminalBlock, {
|
|
287
|
+
command: job.label,
|
|
288
|
+
output: view.text,
|
|
289
|
+
running: live,
|
|
290
|
+
maxLines: Number.POSITIVE_INFINITY,
|
|
291
|
+
labels
|
|
292
|
+
})
|
|
293
|
+
]
|
|
294
|
+
}) : null]
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Session-header entry point for this session's background jobs. Mounting it
|
|
299
|
+
* keeps the session's roster stream open; it renders nothing at all until the
|
|
300
|
+
* session can see at least one job. Expanding an observable row (a live job,
|
|
301
|
+
* or a settled one with retained output) starts its observation stream, and
|
|
302
|
+
* collapsing (or closing the popover) stops it — output only flows while
|
|
303
|
+
* someone is watching. A running row carries a two-press stop button that
|
|
304
|
+
* requests a human kill through the job controller.
|
|
305
|
+
* @param props - runtime slot currency, the jobs snapshot hook, the roster,
|
|
306
|
+
* observation, and kill controls, and the namespace translator.
|
|
307
|
+
* @returns the trigger and its popover list, or null when there is nothing to show.
|
|
308
|
+
*/
|
|
309
|
+
function JobListAction({ sessionId, useJobs, watchRows, observe, killJob, t }) {
|
|
310
|
+
const jobs = useJobs((state) => state.rows[sessionId]) ?? NO_JOBS;
|
|
311
|
+
const observedViews = useJobs((state) => state.observed);
|
|
312
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
313
|
+
const [expandedKey, setExpandedKey] = (0, react.useState)(void 0);
|
|
314
|
+
const [now, setNow] = (0, react.useState)(() => Date.now());
|
|
315
|
+
const [settledOpen, setSettledOpen] = (0, react.useState)(void 0);
|
|
316
|
+
const [clearedKeys, setClearedKeys] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
317
|
+
const [killPhase, setKillPhase] = (0, react.useState)(void 0);
|
|
318
|
+
const rootRef = (0, react.useRef)(null);
|
|
319
|
+
const triggerRef = (0, react.useRef)(null);
|
|
320
|
+
const menuRef = (0, react.useRef)(null);
|
|
321
|
+
const [menuShift, setMenuShift] = (0, react.useState)(0);
|
|
322
|
+
const rows = (0, react.useMemo)(() => ordered(jobs), [jobs]);
|
|
323
|
+
const liveRows = (0, react.useMemo)(() => rows.filter(isLive), [rows]);
|
|
324
|
+
const settledRows = (0, react.useMemo)(() => rows.filter((job) => !isLive(job) && !clearedKeys.has(String(job.id))), [rows, clearedKeys]);
|
|
325
|
+
const settledExpanded = settledOpen ?? liveRows.length === 0;
|
|
326
|
+
const visibleCount = liveRows.length + settledRows.length;
|
|
327
|
+
(0, _qilin_agent_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, setOpen);
|
|
328
|
+
(0, react.useEffect)(() => watchRows(sessionId), [sessionId, watchRows]);
|
|
329
|
+
(0, react.useEffect)(() => {
|
|
330
|
+
if (!open || liveRows.length === 0) return;
|
|
331
|
+
setNow(Date.now());
|
|
332
|
+
const timer = setInterval(() => {
|
|
333
|
+
setNow(Date.now());
|
|
334
|
+
}, 1e3);
|
|
335
|
+
return () => {
|
|
336
|
+
clearInterval(timer);
|
|
337
|
+
};
|
|
338
|
+
}, [open, liveRows.length]);
|
|
339
|
+
(0, react.useLayoutEffect)(() => {
|
|
340
|
+
if (!open) {
|
|
341
|
+
setMenuShift(0);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const fit = () => {
|
|
345
|
+
const root = rootRef.current;
|
|
346
|
+
const menu = menuRef.current;
|
|
347
|
+
/* v8 ignore next -- both refs are attached while the open popover renders. */
|
|
348
|
+
if (root === null || menu === null) return;
|
|
349
|
+
const width = menu.offsetWidth;
|
|
350
|
+
if (width === 0) return;
|
|
351
|
+
const anchorLeft = root.getBoundingClientRect().left;
|
|
352
|
+
setMenuShift(Math.max(VIEWPORT_MARGIN - anchorLeft, Math.min(0, window.innerWidth - VIEWPORT_MARGIN - width - anchorLeft)));
|
|
353
|
+
};
|
|
354
|
+
fit();
|
|
355
|
+
window.addEventListener("resize", fit);
|
|
356
|
+
return () => {
|
|
357
|
+
window.removeEventListener("resize", fit);
|
|
358
|
+
};
|
|
359
|
+
}, [open]);
|
|
360
|
+
const expandedRow = open && expandedKey !== void 0 ? rows.find((job) => String(job.id) === expandedKey) : void 0;
|
|
361
|
+
const activeJob = expandedRow !== void 0 && isObservable(expandedRow) ? expandedRow.id : void 0;
|
|
362
|
+
(0, react.useEffect)(() => {
|
|
363
|
+
if (activeJob === void 0) return;
|
|
364
|
+
return observe(sessionId, activeJob);
|
|
365
|
+
}, [
|
|
366
|
+
sessionId,
|
|
367
|
+
activeJob,
|
|
368
|
+
observe
|
|
369
|
+
]);
|
|
370
|
+
(0, react.useEffect)(() => {
|
|
371
|
+
if (visibleCount === 0 && open) setOpen(false);
|
|
372
|
+
}, [visibleCount, open]);
|
|
373
|
+
(0, react.useEffect)(() => {
|
|
374
|
+
if (expandedKey !== void 0 && !rows.some((job) => String(job.id) === expandedKey)) setExpandedKey(void 0);
|
|
375
|
+
}, [rows, expandedKey]);
|
|
376
|
+
(0, react.useEffect)(() => {
|
|
377
|
+
if (killPhase === void 0 || killPhase.state === "pending") return;
|
|
378
|
+
const timer = setTimeout(() => {
|
|
379
|
+
setKillPhase(void 0);
|
|
380
|
+
}, killPhase.state === "armed" ? KILL_ARM_MS : KILL_FAILED_MS);
|
|
381
|
+
return () => {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
};
|
|
384
|
+
}, [killPhase]);
|
|
385
|
+
(0, react.useEffect)(() => {
|
|
386
|
+
if (killPhase !== void 0 && !rows.some((job) => String(job.id) === killPhase.key && job.status === "running")) setKillPhase(void 0);
|
|
387
|
+
}, [rows, killPhase]);
|
|
388
|
+
const pressKill = (job) => {
|
|
389
|
+
const key = String(job.id);
|
|
390
|
+
if (killPhase?.key !== key || killPhase.state !== "armed") {
|
|
391
|
+
setKillPhase({
|
|
392
|
+
key,
|
|
393
|
+
state: "armed"
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
setKillPhase({
|
|
398
|
+
key,
|
|
399
|
+
state: "pending"
|
|
400
|
+
});
|
|
401
|
+
killJob(sessionId, key).then((ok) => {
|
|
402
|
+
setKillPhase((current) => current?.key === key && !ok ? {
|
|
403
|
+
key,
|
|
404
|
+
state: "failed"
|
|
405
|
+
} : current);
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
if (visibleCount === 0) return null;
|
|
409
|
+
const countLabel = t(liveRows.length > 0 ? liveRows.length === 1 ? "count.live.one" : "count.live.other" : visibleCount === 1 ? "count.idle.one" : "count.idle.other", { count: liveRows.length > 0 ? liveRows.length : visibleCount });
|
|
410
|
+
const clearSettled = () => {
|
|
411
|
+
setClearedKeys((current) => {
|
|
412
|
+
const next = new Set(current);
|
|
413
|
+
for (const job of settledRows) next.add(String(job.id));
|
|
414
|
+
return next;
|
|
415
|
+
});
|
|
416
|
+
if (expandedKey !== void 0 && settledRows.some((job) => String(job.id) === expandedKey)) setExpandedKey(void 0);
|
|
417
|
+
};
|
|
418
|
+
const onKeyDown = (event) => {
|
|
419
|
+
if (event.key !== "Escape" || !open) return;
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
setOpen(false);
|
|
422
|
+
triggerRef.current?.focus();
|
|
423
|
+
};
|
|
424
|
+
const item = (job) => (0, react_jsx_runtime.jsx)(JobItem, {
|
|
425
|
+
job,
|
|
426
|
+
view: isObservable(job) ? observedViews[String(job.id)] : void 0,
|
|
427
|
+
expanded: expandedKey === String(job.id),
|
|
428
|
+
now,
|
|
429
|
+
onToggle: () => {
|
|
430
|
+
setExpandedKey((current) => current === String(job.id) ? void 0 : String(job.id));
|
|
431
|
+
},
|
|
432
|
+
...job.status === "running" ? { kill: {
|
|
433
|
+
state: killPhase?.key === String(job.id) ? killPhase.state : "idle",
|
|
434
|
+
onPress: () => {
|
|
435
|
+
pressKill(job);
|
|
436
|
+
}
|
|
437
|
+
} } : {},
|
|
438
|
+
t
|
|
439
|
+
}, String(job.id));
|
|
440
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
441
|
+
ref: rootRef,
|
|
442
|
+
className: JobListAction_module_css_default.root,
|
|
443
|
+
onKeyDown,
|
|
444
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
445
|
+
ref: triggerRef,
|
|
446
|
+
type: "button",
|
|
447
|
+
className: JobListAction_module_css_default.trigger,
|
|
448
|
+
"aria-expanded": open,
|
|
449
|
+
"aria-label": countLabel,
|
|
450
|
+
onClick: () => {
|
|
451
|
+
setNow(Date.now());
|
|
452
|
+
setOpen((current) => !current);
|
|
453
|
+
},
|
|
454
|
+
children: [
|
|
455
|
+
liveRows.length > 0 ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.StateDot, {
|
|
456
|
+
state: "ongoing",
|
|
457
|
+
className: JobListAction_module_css_default.triggerDot
|
|
458
|
+
}) : null,
|
|
459
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
460
|
+
className: JobListAction_module_css_default.count,
|
|
461
|
+
children: countLabel
|
|
462
|
+
}),
|
|
463
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, {
|
|
464
|
+
size: 12,
|
|
465
|
+
className: open ? JobListAction_module_css_default.triggerOpen : void 0
|
|
466
|
+
})
|
|
467
|
+
]
|
|
468
|
+
}), open ? (0, react_jsx_runtime.jsxs)("ul", {
|
|
469
|
+
ref: menuRef,
|
|
470
|
+
className: JobListAction_module_css_default.menu,
|
|
471
|
+
style: { left: menuShift },
|
|
472
|
+
"aria-label": t("list.aria"),
|
|
473
|
+
children: [
|
|
474
|
+
liveRows.length > 0 ? (0, react_jsx_runtime.jsx)("li", {
|
|
475
|
+
className: JobListAction_module_css_default.sectionHeader,
|
|
476
|
+
"aria-hidden": "true",
|
|
477
|
+
children: t("section.live")
|
|
478
|
+
}) : null,
|
|
479
|
+
liveRows.map(item),
|
|
480
|
+
settledRows.length > 0 ? (0, react_jsx_runtime.jsxs)("li", {
|
|
481
|
+
className: JobListAction_module_css_default.sectionHeader,
|
|
482
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
483
|
+
type: "button",
|
|
484
|
+
className: JobListAction_module_css_default.sectionToggle,
|
|
485
|
+
"aria-expanded": settledExpanded,
|
|
486
|
+
onClick: () => {
|
|
487
|
+
setSettledOpen(!settledExpanded);
|
|
488
|
+
},
|
|
489
|
+
children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, {
|
|
490
|
+
size: 12,
|
|
491
|
+
className: settledExpanded ? `${JobListAction_module_css_default.sectionChevron} ${JobListAction_module_css_default.sectionChevronOpen}` : JobListAction_module_css_default.sectionChevron
|
|
492
|
+
}), t("section.settledCount", { count: settledRows.length })]
|
|
493
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
494
|
+
type: "button",
|
|
495
|
+
className: JobListAction_module_css_default.sectionClear,
|
|
496
|
+
onClick: clearSettled,
|
|
497
|
+
children: t("section.clear")
|
|
498
|
+
})]
|
|
499
|
+
}) : null,
|
|
500
|
+
settledExpanded ? settledRows.map(item) : null
|
|
501
|
+
]
|
|
502
|
+
}) : null]
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region lib/types/client/locales.js
|
|
507
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
508
|
+
const zh = {
|
|
509
|
+
"count.live.one": "{count} 个后台任务运行中",
|
|
510
|
+
"count.live.other": "{count} 个后台任务运行中",
|
|
511
|
+
"count.idle.one": "{count} 个后台任务",
|
|
512
|
+
"count.idle.other": "{count} 个后台任务",
|
|
513
|
+
"list.aria": "后台任务",
|
|
514
|
+
"section.live": "进行中",
|
|
515
|
+
"section.settledCount": "已结束 {count}",
|
|
516
|
+
"section.clear": "清空",
|
|
517
|
+
"row.expandAria": "展开 {label} 的实时输出",
|
|
518
|
+
"row.collapseAria": "收起 {label} 的实时输出",
|
|
519
|
+
"kill.stop": "停止任务 {label}",
|
|
520
|
+
"kill.confirm": "再次点击确认停止",
|
|
521
|
+
"kill.confirmAction": "确认停止",
|
|
522
|
+
"kill.failed": "停止失败",
|
|
523
|
+
"status.running": "运行中",
|
|
524
|
+
"status.stopping": "正在停止",
|
|
525
|
+
"status.completed": "已完成",
|
|
526
|
+
"status.killed": "已取消",
|
|
527
|
+
"status.failed": "已失败",
|
|
528
|
+
"duration.seconds": "{seconds}秒",
|
|
529
|
+
"duration.minutes": "{minutes}分{seconds}秒",
|
|
530
|
+
"duration.hours": "{hours}小时{minutes}分",
|
|
531
|
+
"duration.title.live": "已运行 {duration}",
|
|
532
|
+
"duration.title.done": "耗时 {duration}",
|
|
533
|
+
"output.gap": "……较早的输出已丢弃……",
|
|
534
|
+
"output.error": "实时输出流中断:{error}",
|
|
535
|
+
"terminal.signal": "信号 {signal}",
|
|
536
|
+
"terminal.exitCode": "退出码 {code}",
|
|
537
|
+
"terminal.noExitCode": "未正常退出",
|
|
538
|
+
"terminal.running": "运行中",
|
|
539
|
+
"terminal.failed": "已失败",
|
|
540
|
+
"terminal.done": "已完成",
|
|
541
|
+
"terminal.copy": "复制",
|
|
542
|
+
"terminal.copied": "已复制",
|
|
543
|
+
"terminal.noOutput": "(无输出)",
|
|
544
|
+
"terminal.collapse": "收起",
|
|
545
|
+
"terminal.collapseAria": "收起输出",
|
|
546
|
+
"terminal.expand": "展开其余 {n} 行",
|
|
547
|
+
"terminal.expandAria": "展开被折叠的 {n} 行输出"
|
|
548
|
+
};
|
|
549
|
+
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
550
|
+
const en = {
|
|
551
|
+
"count.live.one": "{count} background job running",
|
|
552
|
+
"count.live.other": "{count} background jobs running",
|
|
553
|
+
"count.idle.one": "{count} background job",
|
|
554
|
+
"count.idle.other": "{count} background jobs",
|
|
555
|
+
"list.aria": "Background jobs",
|
|
556
|
+
"section.live": "Running",
|
|
557
|
+
"section.settledCount": "Finished {count}",
|
|
558
|
+
"section.clear": "Clear",
|
|
559
|
+
"row.expandAria": "Show live output of {label}",
|
|
560
|
+
"row.collapseAria": "Hide live output of {label}",
|
|
561
|
+
"kill.stop": "Stop task {label}",
|
|
562
|
+
"kill.confirm": "Click again to confirm",
|
|
563
|
+
"kill.confirmAction": "Confirm stop",
|
|
564
|
+
"kill.failed": "Stop failed",
|
|
565
|
+
"status.running": "running",
|
|
566
|
+
"status.stopping": "stopping",
|
|
567
|
+
"status.completed": "completed",
|
|
568
|
+
"status.killed": "cancelled",
|
|
569
|
+
"status.failed": "failed",
|
|
570
|
+
"duration.seconds": "{seconds}s",
|
|
571
|
+
"duration.minutes": "{minutes}m {seconds}s",
|
|
572
|
+
"duration.hours": "{hours}h {minutes}m",
|
|
573
|
+
"duration.title.live": "Running for {duration}",
|
|
574
|
+
"duration.title.done": "Took {duration}",
|
|
575
|
+
"output.gap": "… earlier output dropped …",
|
|
576
|
+
"output.error": "live output stream interrupted: {error}",
|
|
577
|
+
"terminal.signal": "signal {signal}",
|
|
578
|
+
"terminal.exitCode": "exit {code}",
|
|
579
|
+
"terminal.noExitCode": "no exit code",
|
|
580
|
+
"terminal.running": "running",
|
|
581
|
+
"terminal.failed": "failed",
|
|
582
|
+
"terminal.done": "done",
|
|
583
|
+
"terminal.copy": "Copy",
|
|
584
|
+
"terminal.copied": "Copied",
|
|
585
|
+
"terminal.noOutput": "(no output)",
|
|
586
|
+
"terminal.collapse": "Collapse",
|
|
587
|
+
"terminal.collapseAria": "Collapse output",
|
|
588
|
+
"terminal.expand": "Show {n} more lines",
|
|
589
|
+
"terminal.expandAria": "Expand {n} collapsed output lines"
|
|
590
|
+
};
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region lib/types/client/index.js
|
|
593
|
+
/** Required services: the jobs rosters, observations, and kill, the slot registry, and dictionaries. */
|
|
594
|
+
const inject = [
|
|
595
|
+
"jobs",
|
|
596
|
+
"slots",
|
|
597
|
+
"locale"
|
|
598
|
+
];
|
|
599
|
+
/**
|
|
600
|
+
* Client plugin body: register the dictionaries and the header action.
|
|
601
|
+
* @param ctx - client root context.
|
|
602
|
+
*/
|
|
603
|
+
function apply(ctx) {
|
|
604
|
+
ctx.effect(() => ctx.locale.register("job", {
|
|
605
|
+
zh,
|
|
606
|
+
en
|
|
607
|
+
}), "ui-jobs: dictionaries");
|
|
608
|
+
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
609
|
+
name: "conversation.session.header.actions",
|
|
610
|
+
id: "job-list",
|
|
611
|
+
order: 20,
|
|
612
|
+
locale: "job",
|
|
613
|
+
inject: () => ({
|
|
614
|
+
hooks: { jobs: ctx.jobs.state },
|
|
615
|
+
watchRows: (sessionId) => ctx.jobs.watchRows(sessionId),
|
|
616
|
+
observe: (sessionId, id) => ctx.jobs.observe(sessionId, id),
|
|
617
|
+
killJob: async (sessionId, jobId) => (await ctx.jobs.kill(sessionId, jobId)).ok
|
|
618
|
+
})
|
|
619
|
+
}, JobListAction));
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
622
|
+
exports.apply = apply;
|
|
623
|
+
exports.inject = inject;
|
|
624
|
+
return module.exports;
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region lib/types/index.js
|
|
2
|
+
/**
|
|
3
|
+
* Node half of the background-job list plugin. The browser half in
|
|
4
|
+
* `src/client/` owns every contribution; this entry exists so the package
|
|
5
|
+
* appears as an ordinary Loader row.
|
|
6
|
+
* @module @qilin-agent/client-ui-jobs
|
|
7
|
+
*/
|
|
8
|
+
/** Loader-visible no-op body; the browser half carries the feature. */
|
|
9
|
+
function apply() {}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { apply };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JobsSnapshot, JobView } from '@qilin-agent/api-job-controller/client';
|
|
2
|
+
import type { SessionId } from '@qilin-agent/session/types';
|
|
3
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@qilin-agent/client-ui-slots';
|
|
4
|
+
import { NS } from './locales.ts';
|
|
5
|
+
/** Registration-side business face for the job list. */
|
|
6
|
+
export interface JobListInjected {
|
|
7
|
+
hooks: {
|
|
8
|
+
/** Client jobs snapshot (rosters and observations) bound by the renderer as useJobs. */
|
|
9
|
+
jobs: {
|
|
10
|
+
getSnapshot(): JobsSnapshot;
|
|
11
|
+
subscribe(listener: () => void): () => void;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Keep one session's roster current while the list is mounted; returns the
|
|
16
|
+
* stop function. Reference-counted by the client service.
|
|
17
|
+
*/
|
|
18
|
+
watchRows: (sessionId: SessionId) => () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Start observing one job's live output; returns the stop function.
|
|
21
|
+
* Reference-counted by the client service, so panels can overlap safely.
|
|
22
|
+
*/
|
|
23
|
+
observe: (sessionId: SessionId | undefined, id: JobView['id']) => () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Kill one background job on the human's behalf. Resolves `true` when the
|
|
26
|
+
* registry admitted the request (`requested` or `already-finished`); row
|
|
27
|
+
* state itself converges through the jobs control frames.
|
|
28
|
+
*/
|
|
29
|
+
killJob: (sessionId: SessionId, jobId: string) => Promise<boolean>;
|
|
30
|
+
}
|
|
31
|
+
/** Full props for the session-header job-list action. */
|
|
32
|
+
export type JobListActionProps = PropsRuntime<'conversation.session.header.actions'> & PropsLocale<typeof NS> & InjectFace<JobListInjected>;
|
|
33
|
+
/**
|
|
34
|
+
* Session-header entry point for this session's background jobs. Mounting it
|
|
35
|
+
* keeps the session's roster stream open; it renders nothing at all until the
|
|
36
|
+
* session can see at least one job. Expanding an observable row (a live job,
|
|
37
|
+
* or a settled one with retained output) starts its observation stream, and
|
|
38
|
+
* collapsing (or closing the popover) stops it — output only flows while
|
|
39
|
+
* someone is watching. A running row carries a two-press stop button that
|
|
40
|
+
* requests a human kill through the job controller.
|
|
41
|
+
* @param props - runtime slot currency, the jobs snapshot hook, the roster,
|
|
42
|
+
* observation, and kill controls, and the namespace translator.
|
|
43
|
+
* @returns the trigger and its popover list, or null when there is nothing to show.
|
|
44
|
+
*/
|
|
45
|
+
export declare function JobListAction({ sessionId, useJobs, watchRows, observe, killJob, t }: JobListActionProps): import("react").JSX.Element | null;
|
|
46
|
+
//# sourceMappingURL=JobListAction.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-job plugin, browser half: contributes one session-header action
|
|
3
|
+
* that renders this session's jobs. Job rows, per-row observation streams,
|
|
4
|
+
* and the human kill all go through the `jobs` client service; this plugin
|
|
5
|
+
* holds no transport state of its own.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context as ClientContext } from '@qilin-agent/kylin';
|
|
8
|
+
import { type JobKey } from './locales.ts';
|
|
9
|
+
declare module '@qilin-agent/client-ui-slots' {
|
|
10
|
+
interface LocaleNamespaceMap {
|
|
11
|
+
/** Background-job list copy. */
|
|
12
|
+
'job': JobKey;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export type { JobListActionProps, JobListInjected } from './JobListAction.tsx';
|
|
16
|
+
/** Required services: the jobs rosters, observations, and kill, the slot registry, and dictionaries. */
|
|
17
|
+
export declare const inject: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Client plugin body: register the dictionaries and the header action.
|
|
20
|
+
* @param ctx - client root context.
|
|
21
|
+
*/
|
|
22
|
+
export declare function apply(ctx: ClientContext): void;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** `job` namespace dictionaries. */
|
|
2
|
+
/** Dictionary namespace owned by this plugin. */
|
|
3
|
+
export declare const NS = "job";
|
|
4
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
5
|
+
export declare const zh: {
|
|
6
|
+
readonly 'count.live.one': "{count} 个后台任务运行中";
|
|
7
|
+
readonly 'count.live.other': "{count} 个后台任务运行中";
|
|
8
|
+
readonly 'count.idle.one': "{count} 个后台任务";
|
|
9
|
+
readonly 'count.idle.other': "{count} 个后台任务";
|
|
10
|
+
readonly 'list.aria': "后台任务";
|
|
11
|
+
readonly 'section.live': "进行中";
|
|
12
|
+
readonly 'section.settledCount': "已结束 {count}";
|
|
13
|
+
readonly 'section.clear': "清空";
|
|
14
|
+
readonly 'row.expandAria': "展开 {label} 的实时输出";
|
|
15
|
+
readonly 'row.collapseAria': "收起 {label} 的实时输出";
|
|
16
|
+
readonly 'kill.stop': "停止任务 {label}";
|
|
17
|
+
readonly 'kill.confirm': "再次点击确认停止";
|
|
18
|
+
readonly 'kill.confirmAction': "确认停止";
|
|
19
|
+
readonly 'kill.failed': "停止失败";
|
|
20
|
+
readonly 'status.running': "运行中";
|
|
21
|
+
readonly 'status.stopping': "正在停止";
|
|
22
|
+
readonly 'status.completed': "已完成";
|
|
23
|
+
readonly 'status.killed': "已取消";
|
|
24
|
+
readonly 'status.failed': "已失败";
|
|
25
|
+
readonly 'duration.seconds': "{seconds}秒";
|
|
26
|
+
readonly 'duration.minutes': "{minutes}分{seconds}秒";
|
|
27
|
+
readonly 'duration.hours': "{hours}小时{minutes}分";
|
|
28
|
+
readonly 'duration.title.live': "已运行 {duration}";
|
|
29
|
+
readonly 'duration.title.done': "耗时 {duration}";
|
|
30
|
+
readonly 'output.gap': "……较早的输出已丢弃……";
|
|
31
|
+
readonly 'output.error': "实时输出流中断:{error}";
|
|
32
|
+
readonly 'terminal.signal': "信号 {signal}";
|
|
33
|
+
readonly 'terminal.exitCode': "退出码 {code}";
|
|
34
|
+
readonly 'terminal.noExitCode': "未正常退出";
|
|
35
|
+
readonly 'terminal.running': "运行中";
|
|
36
|
+
readonly 'terminal.failed': "已失败";
|
|
37
|
+
readonly 'terminal.done': "已完成";
|
|
38
|
+
readonly 'terminal.copy': "复制";
|
|
39
|
+
readonly 'terminal.copied': "已复制";
|
|
40
|
+
readonly 'terminal.noOutput': "(无输出)";
|
|
41
|
+
readonly 'terminal.collapse': "收起";
|
|
42
|
+
readonly 'terminal.collapseAria': "收起输出";
|
|
43
|
+
readonly 'terminal.expand': "展开其余 {n} 行";
|
|
44
|
+
readonly 'terminal.expandAria': "展开被折叠的 {n} 行输出";
|
|
45
|
+
};
|
|
46
|
+
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
47
|
+
export declare const en: Record<JobKey, string>;
|
|
48
|
+
/** Key domain of the `job` namespace (zh is the source of truth). */
|
|
49
|
+
export type JobKey = keyof typeof zh;
|
|
50
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node half of the background-job list plugin. The browser half in
|
|
3
|
+
* `src/client/` owns every contribution; this entry exists so the package
|
|
4
|
+
* appears as an ordinary Loader row.
|
|
5
|
+
* @module @qilin-agent/client-ui-jobs
|
|
6
|
+
*/
|
|
7
|
+
/** Loader-visible no-op body; the browser half carries the feature. */
|
|
8
|
+
export declare function apply(): void;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/client-ui-jobs",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Session-header background-job list with on-demand streaming record panels",
|
|
4
|
+
"version": "3.1.3",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./src/*": "./src/*",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"qilin": {
|
|
21
|
+
"client": {
|
|
22
|
+
"inject": [
|
|
23
|
+
"@qilin-agent/api-job-controller",
|
|
24
|
+
"@qilin-agent/client-locale",
|
|
25
|
+
"@qilin-agent/client-ui-conversation",
|
|
26
|
+
"@qilin-agent/client-ui-primitives"
|
|
27
|
+
],
|
|
28
|
+
"platform": "web"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
35
|
+
"directory": "packages/client/ui-jobs"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/react": "~18.3.1",
|
|
48
|
+
"react": "^18.2.0",
|
|
49
|
+
"@qilin-agent/client-test-runtime": "3.1.3",
|
|
50
|
+
"@qilin-agent/client-locale": "3.1.3",
|
|
51
|
+
"@qilin-agent/client-ui-primitives": "3.1.3",
|
|
52
|
+
"@qilin-agent/client-ui-slots": "3.1.3",
|
|
53
|
+
"@qilin-agent/api-job-controller": "3.1.3",
|
|
54
|
+
"@qilin-agent/kylin": "~4.0.3",
|
|
55
|
+
"@qilin-agent/client-ui-conversation": "3.1.3",
|
|
56
|
+
"@qilin-agent/client-ui-renderer": "3.1.3",
|
|
57
|
+
"@qilin-agent/client-ui-session": "3.1.3",
|
|
58
|
+
"@qilin-agent/jobs": "3.1.3",
|
|
59
|
+
"@qilin-agent/session": "3.1.3"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"lib/index.js",
|
|
63
|
+
"lib/client.js",
|
|
64
|
+
"lib/types/**/*.d.ts"
|
|
65
|
+
],
|
|
66
|
+
"scripts": {
|
|
67
|
+
"bundle": "tsdown",
|
|
68
|
+
"watch": "tsdown --watch"
|
|
69
|
+
}
|
|
6
70
|
}
|