diff --git a/DESIGN.md b/DESIGN.md
index 5e8cb1d..99da36c 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -155,6 +155,7 @@ Flash 的最终 AI 判定与分析独立配置,默认关闭;首页功能配
- 通常以 4、6、8、12、16、24px 组织内部间距;沿用组件现有网格和断点。子容器允许收缩,长内容使用 `min-width: 0` 及合适换行。
- 探索面板在 600px 以下收紧外边距并允许操作换行;模型选择控件宽度跟随容器。窄屏仍可辨认状态、选择来源和发送指导。
- 列表后台刷新保留滚动、选择和折叠状态;首次加载与静默更新分开,避免周期性加载提示挤动内容。实时连接复用现有页面可见性管理。
+- 结束任务的错误与阻塞说明默认只占一行,点击展开安全 Markdown 正文与已有依据,长正文限高滚动。摘要优先采用已保存的有效结论原因,否则取执行错误首行;不新增 AI 复盘请求,不改写原始记录。展开说明不触发任务切换,刷新保留展开状态。
## Elevation
diff --git a/README.md b/README.md
index 854cc18..de91c79 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
基于 [ARTEMIS](https://github.com/google/artemis) 扩展的 Android AI 自动化测试平台。用自然语言描述任务,即可通过简体中文网页或 MCP 驱动真机、模拟器执行操作并收集结果。模型统一使用 **OpenAI Responses API**。
-**2.4.3**:异常模型输出耗尽重试后只失败当前任务,避免触发全局暂停;补充请求关联和有界参数摘要,便于定位流式异常。见 [2.4.3 发行说明](docs/releases/2.4.3.md)。人工评审插件保持 1.1.0;列表优化见 [2.4.2](docs/releases/2.4.2.md),安装修复见 [2.4.1](docs/releases/2.4.1.md)。
+**2.4.4**:任务结束说明默认显示一行摘要,点击展开 Markdown 正文和已记录依据,避免失败长文本占满列表;不新增 AI 复盘请求。见 [2.4.4 发行说明](docs/releases/2.4.4.md)。人工评审插件保持 1.1.0;批次异常隔离见 [2.4.3](docs/releases/2.4.3.md),列表优化见 [2.4.2](docs/releases/2.4.2.md),安装修复见 [2.4.1](docs/releases/2.4.1.md)。
## 主要功能
diff --git a/apps/showcase_ui/package-lock.json b/apps/showcase_ui/package-lock.json
index 32042e3..3617cee 100644
--- a/apps/showcase_ui/package-lock.json
+++ b/apps/showcase_ui/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "artemis-showcase-ui",
- "version": "2.4.3",
+ "version": "2.4.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "artemis-showcase-ui",
- "version": "2.4.3",
+ "version": "2.4.4",
"dependencies": {
"@angular/cdk": "^22.2.4",
"@angular/common": "^22.2.3",
diff --git a/apps/showcase_ui/package.json b/apps/showcase_ui/package.json
index 2c741e9..344663e 100644
--- a/apps/showcase_ui/package.json
+++ b/apps/showcase_ui/package.json
@@ -1,6 +1,6 @@
{
"name": "artemis-showcase-ui",
- "version": "2.4.3",
+ "version": "2.4.4",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html
index 346642e..fb3f763 100644
--- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html
+++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html
@@ -129,9 +129,7 @@ limitations under the License.
{{ session.display_name || session.initial_goal || '未命名任务' }}
- @if (session.error && getTaskStatus(session) === 'failed') {
- {{session.error}}
- }
+
@@ -119,7 +119,7 @@
@if(j.human_reviewed){人工已审}
- @if(j.error){{{j.error}}}
+
}
}
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
index ba490f8..9d09a49 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
@@ -52,7 +52,6 @@ details{margin:14px 0;}summary{color:var(--app-text-secondary, #475569);cursor:p
.report-heading{display:flex;align-items:flex-start;gap:8px;}.report-heading .report-title{flex:1;min-width:0;}
.report-title{display:block;font-size:14px;line-height:1.5;color:var(--app-text-strong, #0f172a);overflow-wrap:anywhere;}
.report-badges{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px;span{padding:3px 8px;border:1px solid var(--app-primary-border, #bfdbfe);border-radius:20px;background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1e40af);font-size:12px;&.failed{background:var(--app-error-soft, #fff1f2);border-color:var(--app-error-border, #fecdd3);color:var(--app-error-text, #be123c);}}}
-.report-error{margin-top:11px;padding:10px 12px;background:var(--app-error-soft, #fff1f2);border-left:3px solid #e11d48;border-radius:6px;color:var(--app-error-text, #9f1239);strong{font-size:12px;}p{margin:4px 0 0;color:var(--app-error-text, #9f1239);white-space:pre-wrap;}}
.report-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:12px 0;div{min-width:0;padding:10px 12px;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;background:var(--app-surface, #fff);}strong{font-size:12px;color:var(--app-text-secondary, #475569);}p{margin:5px 0 0;white-space:pre-wrap;color:var(--app-text-control, #1e293b);}}
.report-content{margin-top:14px;padding:14px;border:1px solid var(--app-border, #e2e8f0);border-radius:10px;background:var(--app-surface, #fff);h3{margin-bottom:12px;}}
.report-body{overflow-x:auto;overflow-wrap:anywhere;color:var(--app-text-control, #1e293b);line-height:1.75;}
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.spec.ts b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.spec.ts
index 21fdfe1..9a120cb 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.spec.ts
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.spec.ts
@@ -176,7 +176,12 @@ describe('AutomationPanelComponent batch deletion', () => {
});
fixture.detectChanges();
const root = fixture.nativeElement as HTMLElement;
- expect(root.querySelector('.report-error')?.textContent).toContain('连续 3 次');
+ const explanation = root.querySelector('app-task-outcome details')!;
+ expect(explanation.open).toBeFalse();
+ expect(explanation.querySelector('summary')?.textContent).toContain('连续 3 次');
+ explanation.querySelector('summary')!.click();
+ expect(explanation.open).toBeTrue();
+ expect(explanation.querySelector('.body')?.textContent).toContain('连续 3 次');
expect(root.querySelector('.report-body h2')?.textContent).toBe('结果');
expect(root.querySelector('.report-body')?.textContent).toContain('第一行');
expect(root.querySelector('.report-body')?.textContent).toContain('第二行');
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
index c260e79..b4c96c2 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
@@ -1,3 +1,4 @@
+import { TaskOutcomeComponent } from '../task-outcome/task-outcome.component';
import { Component, DestroyRef, computed, effect, inject, output, signal } from '@angular/core';
import { FavoriteButtonComponent } from '../favorite-button/favorite-button.component';
import { ReportMarkdownComponent } from '../report-markdown/report-markdown.component';
@@ -24,7 +25,7 @@ interface ArchiveProgress {
}
@Component({
- selector: 'app-automation-panel', standalone: true, imports: [FavoriteButtonComponent, ReportMarkdownComponent, CommonModule, FormsModule, TestResultBadgeComponent, TestResultCardComponent, TaskPluginsComponent, EvidenceSourcesComponent],
+ selector: 'app-automation-panel', standalone: true, imports: [TaskOutcomeComponent,FavoriteButtonComponent, ReportMarkdownComponent, CommonModule, FormsModule, TestResultBadgeComponent, TestResultCardComponent, TaskPluginsComponent, EvidenceSourcesComponent],
templateUrl: './automation-panel.component.html', styleUrl: './automation-panel.component.scss'
})
export class AutomationPanelComponent {
diff --git a/apps/showcase_ui/src/app/components/task-history/task-history.component.spec.ts b/apps/showcase_ui/src/app/components/task-history/task-history.component.spec.ts
index 221ab52..1cb6753 100644
--- a/apps/showcase_ui/src/app/components/task-history/task-history.component.spec.ts
+++ b/apps/showcase_ui/src/app/components/task-history/task-history.component.spec.ts
@@ -18,6 +18,28 @@ describe('历史任务检索', () => {
});
afterEach(() => http.verify());
+ it('历史错误独立折叠,展开不选择任务且后台刷新保留展开状态', () => {
+ const fixture = TestBed.createComponent(TaskHistoryComponent);
+ const row = {session_id:'blocked', name:'前置条件缺失', status:'failed', start_time:100,
+ error:'[web-blocked] FlashRunner failed: **目标未绑定**\n\n- 未执行删除'};
+ http.expectOne(r => r.url === '/api/automation/history').flush({total:1,items:[row]});
+ fixture.detectChanges();
+ const host = fixture.nativeElement as HTMLElement;
+ const details = host.querySelector('app-task-outcome details')!;
+ expect(details.closest('button')).toBeNull();
+ expect(details.open).toBeFalse();
+ details.querySelector('summary')!.click();
+ expect(details.open).toBeTrue();
+ expect(TestBed.inject(AgentService).selectSession).not.toHaveBeenCalled();
+ fixture.componentInstance.load(true);
+ http.expectOne(r => r.url === '/api/automation/history').flush({total:1,items:[{...row}]});
+ fixture.detectChanges();
+ expect(host.querySelector('app-task-outcome details')).toBe(details);
+ expect(details.open).toBeTrue();
+ expect(details.querySelector('strong')!.textContent).toBe('目标未绑定');
+ fixture.destroy();
+ });
+
it('已终止探索可删除,确认范围后刷新,取消不发请求', () => {
const fixture = TestBed.createComponent(TaskHistoryComponent);
const row = {session_id:'stopped', status:'cancelled', exploration_kind:'exploration', exploration_status:'paused', start_time:100};
@@ -268,7 +290,7 @@ describe('历史任务检索', () => {
expect(row.getAttribute('title')).toBe('请打开设置首页并核对两个真实分类');
expect(row.textContent).toContain('1.1 打开并核对设置');
expect(row.textContent).toContain('总耗时 1分5秒');
- expect(row.textContent).toContain('任务执行超时');
+ expect(fixture.nativeElement.querySelector('app-task-outcome summary').textContent).toContain('任务执行超时');
expect(fixture.nativeElement.querySelector('.status-failed .history-status').textContent).toContain('失败');
fixture.destroy();
});
diff --git a/apps/showcase_ui/src/app/components/task-history/task-history.component.ts b/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
index 4adf722..d11d52a 100644
--- a/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
+++ b/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
@@ -1,3 +1,4 @@
+import { TaskOutcomeComponent } from '../task-outcome/task-outcome.component';
import { TaskIdCopyComponent } from '../task-id-copy/task-id-copy.component';
import { FavoriteButtonComponent } from '../favorite-button/favorite-button.component';
import { TaskModeBadgeComponent } from '../task-mode-badge/task-mode-badge.component';
@@ -9,13 +10,13 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { AgentService } from '../../services/agent.service';
@Component({
- selector:'app-task-history', standalone:true, imports:[CommonModule,FormsModule,TaskIdCopyComponent,TestResultBadgeComponent,TaskModeBadgeComponent,FavoriteButtonComponent],
+ selector:'app-task-history', standalone:true, imports:[TaskOutcomeComponent,CommonModule,FormsModule,TaskIdCopyComponent,TestResultBadgeComponent,TaskModeBadgeComponent,FavoriteButtonComponent],
template:`历史任务 {{total()}} 条{{loading() ? '正在查询…' : ''}}
@if(error()){{{error()}}
}
@for(group of groups(); track group.key){{{group.title}} 本页 {{group.rows.length}} 条
- @for(row of group.rows; track row.session_id){@if(agent.canDeleteSession(row)){
}
}
+ @for(row of group.rows; track row.session_id){@if(agent.canDeleteSession(row)){
}
}
}
@if(!loading() && !total()){没有符合条件的历史任务。
}
@@ -51,7 +52,6 @@ import { AgentService } from '../../services/agent.service';
.status-completed .history-status,.status-success .history-status{background:var(--app-success-soft, #dcfce7);color:var(--app-success-text, #166534)}
.status-failed .history-status{background:var(--app-error-soft, #fee2e2);color:var(--app-error-text, #991b1b)}
.status-cancelled .history-status,.status-interrupted .history-status{background:var(--app-warning-soft, #fef3c7);color:var(--app-warning-text, #92400e)}
- .history-error{font-size:11px;color:var(--app-error-text, #b91c1c);line-height:1.4}
.delete-history{position:absolute;right:5px;top:8px;border:0;padding:3px;color:var(--app-text-muted, #64748b);cursor:pointer;font-size:12px}
.delete-history .material-symbols-outlined{font-size:16px}
.delete-history:hover{color:var(--app-error-text, #b91c1c)}
diff --git a/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.spec.ts b/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.spec.ts
new file mode 100644
index 0000000..6ce7e10
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.spec.ts
@@ -0,0 +1,71 @@
+import { TestBed } from '@angular/core/testing';
+import { TaskOutcomeComponent } from './task-outcome.component';
+
+describe('任务结束说明', () => {
+ function create(status = 'failed', error = '[web-example] FlashRunner failed: **前置受阻**\n\n- 未找到目标设备\n- 未执行删除') {
+ const fixture = TestBed.createComponent(TaskOutcomeComponent);
+ fixture.componentRef.setInput('status', status);
+ fixture.componentRef.setInput('error', error);
+ fixture.detectChanges();
+ return fixture;
+ }
+
+ it('默认只显示一行摘要,展开后渲染完整 Markdown,不选择外层任务', () => {
+ const fixture = create();
+ const host = fixture.nativeElement as HTMLElement;
+ host.style.width = '280px';
+ const details = host.querySelector('details')!;
+ expect(details.open).toBeFalse();
+ expect(host.querySelector('.summary-text')!.textContent).toBe('前置受阻');
+ expect(host.textContent).not.toContain('FlashRunner failed');
+ expect(host.querySelector('button details')).toBeNull();
+ const select = jasmine.createSpy('外层任务选择');
+ host.addEventListener('click', select);
+ host.querySelector('summary')!.click();
+ expect(details.open).toBeTrue();
+ expect(select).not.toHaveBeenCalled();
+ expect(host.querySelector('.body strong')!.textContent).toBe('前置受阻');
+ expect(host.querySelectorAll('.body li').length).toBe(2);
+ fixture.componentRef.setInput('error', '[web-example] FlashRunner failed: **更新说明**\n\n- 已有记录');
+ fixture.detectChanges();
+ expect(host.querySelector('details')).toBe(details);
+ expect(details.open).toBeTrue();
+ host.querySelector('summary')!.click();
+ expect(details.open).toBeFalse();
+ });
+
+ it('优先采用已有结论,同时保留不同的执行错误和依据', () => {
+ const fixture = create();
+ fixture.componentRef.setInput('result', {status:'blocked', reason:'目标设备未绑定', evidence:['截图中仅有 **添加设备**']});
+ fixture.detectChanges();
+ const host = fixture.nativeElement as HTMLElement;
+ expect(host.querySelector('.summary-text')!.textContent).toBe('目标设备未绑定');
+ expect(host.textContent).toContain('未执行删除');
+ expect(host.textContent).toContain('已记录依据');
+ expect(host.textContent).toContain('添加设备');
+ });
+
+ it('完成但结论阻塞也展示说明,运行中和无内容时不展示', () => {
+ const fixture = create('running');
+ expect(fixture.nativeElement.querySelector('details')).toBeNull();
+ fixture.componentRef.setInput('status', 'completed');
+ fixture.componentRef.setInput('error', '');
+ fixture.componentRef.setInput('result', {status:'blocked',reason:'缺少预置数据',evidence:[]});
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector('.summary-text').textContent).toBe('缺少预置数据');
+ fixture.componentRef.setInput('result', null);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector('details')).toBeNull();
+ });
+
+ it('不重复相同说明,不执行错误正文中的 HTML 或危险链接', () => {
+ const error = '**失败**\n\n[打开](javascript:alert(1))';
+ const fixture = create('failed', error);
+ fixture.componentRef.setInput('result', {status:'blocked',reason:error,evidence:[]});
+ fixture.detectChanges();
+ const host = fixture.nativeElement as HTMLElement;
+ expect(host.querySelectorAll('app-report-markdown').length).toBe(1);
+ expect(host.querySelector('script')).toBeNull();
+ expect(host.querySelector('a[href^="javascript:"]')).toBeNull();
+ });
+});
diff --git a/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.ts b/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.ts
new file mode 100644
index 0000000..fe7124c
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/task-outcome/task-outcome.component.ts
@@ -0,0 +1,62 @@
+import { Component, computed, input } from '@angular/core';
+import { CaseTestResult } from '../../core/models/session.model';
+import { ReportMarkdownComponent } from '../report-markdown/report-markdown.component';
+
+/** Read-only presentation of persisted results; opening it never requests a model. */
+@Component({
+ selector: 'app-task-outcome',
+ standalone: true,
+ imports: [ReportMarkdownComponent],
+ template: `@if (visible()) {
+
+
+ ›
+ {{summary()}}
+ 详情
+
+
+ @if (reason()) {
}
+ @if (message() && message() !== reason()) {
+ @if (reason()) {
执行记录
}
+
+ }
+ @if (result()?.evidence?.length) {
+
已记录依据
+
@for (item of result()!.evidence; track $index) { }
+ }
+
+
+ }`,
+ styles: [`
+ :host { display: block; min-width: 0; max-width: 100%; }
+ details { margin: 4px 0; font-size: 12px; color: var(--app-text-secondary, #475569); }
+ summary { display: flex; align-items: center; gap: 6px; min-width: 0; min-height: 26px; cursor: pointer; list-style: none; border-radius: 4px; }
+ summary::-webkit-details-marker { display: none; }
+ summary:focus-visible { outline: 2px solid var(--app-primary-text, #1a73e8); outline-offset: 2px; }
+ .summary-text { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .chevron { flex: none; font-size: 18px; line-height: 20px; }
+ details[open] .chevron { transform: rotate(90deg); }
+ .hint { flex: none; font-size: 11px; color: var(--app-text-muted, #64748b); }
+ .body { max-height: 280px; overflow: auto; padding: 8px 10px; margin-top: 4px; border: 1px solid var(--app-border, #e2e8f0); border-radius: 6px; background: var(--app-surface, #fff); cursor: text; }
+ .label { font-size: 11px; color: var(--app-text-muted, #64748b); margin: 10px 0 4px; }
+ ul { margin: 4px 0; padding-left: 20px; }
+ `]
+})
+export class TaskOutcomeComponent {
+ error = input();
+ result = input();
+ status = input('');
+ // Strip only the known SDK envelope, preserving the actual explanation and diagnostics.
+ private explanation(value: string | null | undefined): string {
+ return (value || '').replace(/^\[[^\]\r\n]+\]\s*FlashRunner failed:\s*/, '').trim();
+ }
+ message = computed(() => this.explanation(this.error()));
+ reason = computed(() => this.explanation(this.result()?.reason));
+ visible = computed(() => ['completed', 'success', 'failed', 'cancelled', 'interrupted', 'skipped'].includes(this.status())
+ && !!(this.message() || (this.result()?.status !== 'passed' && this.reason())));
+ summary = computed(() => {
+ const line = (this.reason() || this.message()).split(/\r?\n/).find(part => part.trim()) || '';
+ return line.replace(/^\s{0,3}(?:#{1,6}\s+|[-*+]\s+|>\s*)/, '')
+ .replace(/!?\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/(\*\*|__|~~|`)/g, '');
+ });
+}
diff --git a/docs/releases/2.4.4.md b/docs/releases/2.4.4.md
new file mode 100644
index 0000000..28b82de
--- /dev/null
+++ b/docs/releases/2.4.4.md
@@ -0,0 +1,25 @@
+# ARTEMIS 2.4.4
+
+本版改善任务结束后错误和阻塞说明的阅读体验,基于 2.4.3。人工评审插件保持 **1.1.0**,SDK、其他插件及第三方依赖版本不变。
+
+## 更新
+
+- 历史任务、任务切换列表和批次任务默认只显示一行说明,超长内容省略,点击可展开;报告异常说明采用相同折叠展示。
+- 展开后安全渲染 Markdown 正文、列表和表格,并展示已有结论及已记录依据;长正文限高滚动。
+- 列表摘要优先使用已保存的结论原因,否则使用执行错误首行;展示时移除 FlashRunner 固定技术前缀,原始记录保持不变。
+- 仅展示已结束任务的已有信息,不新增 AI 复盘或模型请求。展开说明不触发任务切换,列表后台刷新保留展开状态。
+
+本版不改变任务执行、模型重试或批次停止策略,继续保留 2.4.3 的异常输出隔离与参数保护。
+
+## 验证
+
+- 前端回归:353 项通过,生产构建通过。
+- 内置浏览器使用隔离样例验证默认折叠、Markdown 展开、长内容、无错误完成任务、批次布局、浅色/深色与窄视口。
+- 最终源码 ZIP 的隔离解压、独立空 npm 缓存、官方源与国内镜像安装和生产构建结果随附件 `INSTALL-VERIFICATION.md` 提供。
+- 未复跑用户业务任务、调用真实模型或进行高并发测试;本次没有 Python 行为变更,未重跑 Python 全量回归。
+
+## 升级
+
+已有安装请等待任务结束,运行 `update-cn.bat` 或 `update.bat` 后重启。新安装下载 `artemis-2.4.4-source.zip`,按 `SHA256SUMS.txt` 校验后启动。请勿直接覆盖旧安装目录。
+
+人工评审插件 1.1.0 无需更新。本次发布不构建 Docker,不重启用户服务。
diff --git a/pyproject.toml b/pyproject.toml
index 1e07ef5..24b7aed 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,7 @@
[project]
name = "artemis"
-version = "2.4.3"
+version = "2.4.4"
description = "Autonomous AI Assistant for Mobile Automation"
authors = [
diff --git a/uv.lock b/uv.lock
index 0875852..9bb28f3 100644
--- a/uv.lock
+++ b/uv.lock
@@ -248,7 +248,7 @@ wheels = [
[[package]]
name = "artemis"
-version = "2.4.3"
+version = "2.4.4"
source = { editable = "." }
dependencies = [
{ name = "adbutils" },