agentworkshop 0.7.3 → 0.7.5
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/B9Yie5HN.js +2 -0
- package/.output/public/_nuxt/CN7Zh8GQ.js +1 -0
- package/.output/public/_nuxt/CdSgfcYR.js +1 -0
- package/.output/public/_nuxt/CdfAP_IH2.js +1 -0
- package/.output/public/_nuxt/ChnfyQHz.js +1 -0
- package/.output/public/_nuxt/{BkKYf8xY.js → CyygA0q4.js} +4 -4
- package/.output/public/_nuxt/{Bz40vyZS.js → DFnGFEav.js} +7 -7
- package/.output/public/_nuxt/Xaf_HIR3.js +1 -0
- package/.output/public/_nuxt/ZugRk0CM2.js +4 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/55c10854-ee0e-4be4-924b-a1b0e100fe11.json +1 -0
- package/.output/public/_nuxt/{default.iGJy9dP-.css → default.D17kXlD6.css} +1 -1
- package/.output/public/_nuxt/monitor.CmwpEUCI.css +1 -0
- package/.output/public/_nuxt/yIKY_V1P.js +1 -0
- package/.output/server/chunks/_/nitro.mjs +1131 -920
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{TownView-BkHxMiwZ.mjs → TownView-Bl8Gkcxi.mjs} +3 -3
- package/.output/server/chunks/build/{TownView-BkHxMiwZ.mjs.map → TownView-Bl8Gkcxi.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-Bna1_JuB.mjs → _id_-CWLFGZjV.mjs} +2 -2
- package/.output/server/chunks/build/{_id_-Bna1_JuB.mjs.map → _id_-CWLFGZjV.mjs.map} +1 -1
- package/.output/server/chunks/build/_id_-GnRJPykX.mjs +1 -1
- package/.output/server/chunks/build/{_wsId_-CzLDGT_V.mjs → _wsId_-BhQMjyHG.mjs} +3 -4
- package/.output/server/chunks/build/{_wsId_-CzLDGT_V.mjs.map → _wsId_-BhQMjyHG.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-CmeM4u5u.mjs → daq-BOgpn9sl.mjs} +4 -5
- package/.output/server/chunks/build/daq-BOgpn9sl.mjs.map +1 -0
- package/.output/server/chunks/build/dcw-BTM5YU-B.mjs +1 -1
- package/.output/server/chunks/build/{default-Dt6xLdpd.mjs → default-CKJWhkot.mjs} +60 -17
- package/.output/server/chunks/build/{default-Dt6xLdpd.mjs.map → default-CKJWhkot.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-CUQt1z7D.mjs → logs-Dyk0I2I9.mjs} +3 -4
- package/.output/server/chunks/build/logs-Dyk0I2I9.mjs.map +1 -0
- package/.output/server/chunks/build/{monitor-yNoV98rf.mjs → monitor-KW8WLOJd.mjs} +34 -33
- package/.output/server/chunks/build/{monitor-yNoV98rf.mjs.map → monitor-KW8WLOJd.mjs.map} +1 -1
- package/.output/server/chunks/build/{town-BN4vQPb8.mjs → town-_4tEEEVW.mjs} +3 -4
- package/.output/server/chunks/build/town-_4tEEEVW.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqStream-hwkhJHFU.mjs +1 -1
- package/.output/server/chunks/build/useDcwStream-Bdew_yrr.mjs +1 -1
- package/.output/server/chunks/build/{useWorkshopWs-xcdeiSmJ.mjs → useWorkshopWs-B3j5wIAK.mjs} +177 -3
- package/.output/server/chunks/build/useWorkshopWs-B3j5wIAK.mjs.map +1 -0
- package/.output/server/chunks/build/{workshop-CLhwe5eo.mjs → workshop-k0XZTFKg.mjs} +2 -3
- package/.output/server/chunks/build/workshop-k0XZTFKg.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +32 -0
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +56 -0
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +17 -11
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README.md +435 -434
- package/app/components/AppHeader.vue +138 -0
- package/app/composables/workshop/useWorkshopWs.ts +4 -0
- package/app/pages/monitor.vue +17 -1
- package/app/stores/workshop/hitl.ts +80 -0
- package/bin/aw.mjs +0 -0
- package/cli/commands/stop.mjs +108 -0
- package/cli/commands/tui.mjs +19 -0
- package/config.yml +134 -134
- package/docs/tui.md +43 -0
- package/i18n/locales/en.ts +3 -0
- package/i18n/locales/zh-CN.ts +3 -0
- package/package.json +140 -144
- package/scripts/_spike-pitui.mjs +116 -0
- package/scripts/e2e-aw-tui-global.mjs +277 -0
- package/scripts/e2e-hitl-live.mjs +323 -0
- package/scripts/e2e-line-live.mjs +144 -0
- package/scripts/test-hitl-registry.ts +112 -0
- package/scripts/test-tui-commands.mjs +148 -0
- package/scripts/test-tui-reducers.mjs +72 -0
- package/scripts/tsconfig.json +14 -0
- package/scripts/tui-smoke.mjs +100 -0
- package/server/api/workshop/hitl/pending.get.ts +28 -0
- package/server/api/workshop/hitl/respond.post.ts +67 -0
- package/server/api/workshop/ws.ts +30 -1
- package/server/data/daqs.json +39 -17
- package/server/data/dcw-lines.json +14 -0
- package/server/data/dcw-products.json +35 -0
- package/server/data/dcw-recipes.json +140 -0
- package/server/data/dcw-rollback.json +111 -1
- package/server/data/dcw-runs.json +135 -90
- package/server/data/dcw-writes.json +120 -0
- package/server/data/dcws.json +400 -275
- package/server/data/line-runs.json +11 -1
- package/server/plugins/workshop.ts +7 -0
- package/server/services/workshop/agents/harness-terminal.ts +92 -25
- package/server/services/workshop/agents/hitl-registry.ts +181 -0
- package/server/services/workshop/agents/tool-approvals.ts +15 -0
- package/server/services/workshop/scene-events.ts +19 -9
- package/shared/workshop-protocol.ts +37 -0
- package/tui/README.md +74 -0
- package/tui/aw-tui.mjs +406 -0
- package/tui/commands/index.mjs +287 -0
- package/tui/components/chat-log.mjs +42 -0
- package/tui/components/hitl-card.mjs +36 -0
- package/tui/components/monitor-pane.mjs +39 -0
- package/tui/components/root.mjs +53 -0
- package/tui/components/status-bar.mjs +33 -0
- package/tui/lib/api.mjs +82 -0
- package/tui/lib/config.mjs +58 -0
- package/tui/lib/pickers.mjs +102 -0
- package/tui/lib/reducers.mjs +218 -0
- package/tui/lib/state.mjs +86 -0
- package/tui/lib/virtual-terminal.mjs +48 -0
- package/tui/lib/ws.mjs +124 -0
- package/tui/theme.mjs +38 -0
- package/.output/public/_nuxt/B7V9IfTL.js +0 -1
- package/.output/public/_nuxt/BE5QyG5J2.js +0 -1
- package/.output/public/_nuxt/BuKI8z-5.js +0 -1
- package/.output/public/_nuxt/D0C5Q0di.js +0 -1
- package/.output/public/_nuxt/DC-QMEk1.js +0 -2
- package/.output/public/_nuxt/DEkQ0qQ02.js +0 -1
- package/.output/public/_nuxt/builds/meta/c6c01cdd-762e-4fb8-86e7-753c5185b856.json +0 -1
- package/.output/public/_nuxt/dt2tKE95.js +0 -4
- package/.output/public/_nuxt/i_MKObHo.js +0 -1
- package/.output/public/_nuxt/monitor.41fCD6TB.css +0 -1
- package/.output/public/_nuxt/tpDMVY5w.js +0 -1
- package/.output/server/chunks/build/connection-Dc0fn6yd.mjs +0 -120
- package/.output/server/chunks/build/connection-Dc0fn6yd.mjs.map +0 -1
- package/.output/server/chunks/build/daq-CmeM4u5u.mjs.map +0 -1
- package/.output/server/chunks/build/logs-CUQt1z7D.mjs.map +0 -1
- package/.output/server/chunks/build/town-BN4vQPb8.mjs.map +0 -1
- package/.output/server/chunks/build/useWorkshopWs-xcdeiSmJ.mjs.map +0 -1
- package/.output/server/chunks/build/workshop-CLhwe5eo.mjs.map +0 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { MenuProps, SelectProps } from 'ant-design-vue'
|
|
3
|
+
import type { AepHitlItem } from '#shared/workshop-protocol'
|
|
3
4
|
import { useUserStore } from '@/app/stores/workshop/user'
|
|
4
5
|
import { useWsConnectionStore } from '@/app/stores/workshop/connection'
|
|
6
|
+
import { useHitlStore } from '@/app/stores/workshop/hitl'
|
|
7
|
+
import { useWorkshopWs } from '@/app/composables/workshop/useWorkshopWs'
|
|
5
8
|
|
|
6
9
|
const { t, locale, locales, setLocale } = useI18n()
|
|
7
10
|
const store = useAppStore()
|
|
@@ -10,6 +13,26 @@ const trail = useRouteTrailStore()
|
|
|
10
13
|
const userStore = useUserStore()
|
|
11
14
|
const { metaFor } = useRouteMeta()
|
|
12
15
|
|
|
16
|
+
// ── HITL 全局待办(omp ask 对话框 + dcw 审批统一徽标;页头保底建连 ——
|
|
17
|
+
// hitl 全员直推帧只达已连 peer,不建连的页面收不到提醒;快照兜底刷新前待办) ──
|
|
18
|
+
const hitl = useHitlStore()
|
|
19
|
+
const wsSession = useWorkshopWs()
|
|
20
|
+
const ensureHitlLive = () => {
|
|
21
|
+
if (userStore.isLoggedIn) wsSession.ensureConnected()
|
|
22
|
+
}
|
|
23
|
+
watch(() => userStore.token, (t2) => {
|
|
24
|
+
if (t2) {
|
|
25
|
+
ensureHitlLive()
|
|
26
|
+
void hitl.loadSnapshot()
|
|
27
|
+
}
|
|
28
|
+
else hitl.clear()
|
|
29
|
+
}, { immediate: true })
|
|
30
|
+
const hitlKindLabel = (kind: AepHitlItem['kind']) =>
|
|
31
|
+
kind === 'omp-dialog' ? t('appHeader.hitlKindOmp') : t('appHeader.hitlKindDcw')
|
|
32
|
+
const hitlGo = (item: AepHitlItem) => {
|
|
33
|
+
navigateTo({ path: '/monitor', query: { agentId: item.agentId, channelId: item.channelId } })
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
const localeOptions = computed(() =>
|
|
14
37
|
(locales.value as Array<{ code: string, name: string }>).map(l => ({
|
|
15
38
|
label: l.name,
|
|
@@ -205,6 +228,40 @@ const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
|
|
|
205
228
|
|
|
206
229
|
<!-- 右侧:功能集群 -->
|
|
207
230
|
<div class="header-right">
|
|
231
|
+
<!-- HITL 待办铃标(有待办才出现;点击下拉 → 进入对应 agent 终端处理) -->
|
|
232
|
+
<ClientOnly>
|
|
233
|
+
<a-dropdown
|
|
234
|
+
v-if="hitl.count > 0"
|
|
235
|
+
placement="bottomRight"
|
|
236
|
+
>
|
|
237
|
+
<button
|
|
238
|
+
class="icon-btn hitl-bell"
|
|
239
|
+
:title="t('appHeader.hitlBadge')"
|
|
240
|
+
>
|
|
241
|
+
<span class="i-tabler-bell-ringing" />
|
|
242
|
+
<span class="hitl-count">{{ hitl.count }}</span>
|
|
243
|
+
</button>
|
|
244
|
+
<template #overlay>
|
|
245
|
+
<div class="hitl-menu">
|
|
246
|
+
<div class="hitl-menu-title">
|
|
247
|
+
{{ t('appHeader.hitlBadge') }} · {{ hitl.count }}
|
|
248
|
+
</div>
|
|
249
|
+
<button
|
|
250
|
+
v-for="item in hitl.items"
|
|
251
|
+
:key="`${item.kind}:${item.id}`"
|
|
252
|
+
class="hitl-item"
|
|
253
|
+
@click="hitlGo(item)"
|
|
254
|
+
>
|
|
255
|
+
<span class="hitl-item-top">
|
|
256
|
+
<span class="hitl-item-agent">{{ item.agentName }}</span>
|
|
257
|
+
<span class="hitl-item-kind">{{ hitlKindLabel(item.kind) }}</span>
|
|
258
|
+
</span>
|
|
259
|
+
<span class="hitl-item-title">{{ item.title }}</span>
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</template>
|
|
263
|
+
</a-dropdown>
|
|
264
|
+
</ClientOnly>
|
|
208
265
|
<!-- 实时连接状态点(WS 会话全局单例;未用过 WS 的会话不显示) -->
|
|
209
266
|
<span
|
|
210
267
|
v-if="wsVisible"
|
|
@@ -660,6 +717,87 @@ const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
|
|
|
660
717
|
50% { opacity: 0.45; }
|
|
661
718
|
}
|
|
662
719
|
|
|
720
|
+
/* ── HITL 待办铃标(琥珀警示;角标数字极简,无装饰堆砌) ── */
|
|
721
|
+
.hitl-bell {
|
|
722
|
+
position: relative;
|
|
723
|
+
color: var(--tone-warning-dot, #d4a017);
|
|
724
|
+
}
|
|
725
|
+
.hitl-count {
|
|
726
|
+
position: absolute;
|
|
727
|
+
top: 2px;
|
|
728
|
+
right: 2px;
|
|
729
|
+
min-width: 15px;
|
|
730
|
+
height: 15px;
|
|
731
|
+
padding: 0 4px;
|
|
732
|
+
font-family: var(--font-mono);
|
|
733
|
+
font-size: 9.5px;
|
|
734
|
+
font-weight: 700;
|
|
735
|
+
line-height: 15px;
|
|
736
|
+
color: var(--on-accent, #fff);
|
|
737
|
+
text-align: center;
|
|
738
|
+
background: var(--tone-danger-dot, #e05252);
|
|
739
|
+
border-radius: 8px;
|
|
740
|
+
}
|
|
741
|
+
.hitl-menu {
|
|
742
|
+
min-width: 260px;
|
|
743
|
+
max-width: 340px;
|
|
744
|
+
padding: 6px;
|
|
745
|
+
background: var(--paper, #fff);
|
|
746
|
+
border: 1px solid var(--line);
|
|
747
|
+
border-radius: var(--radius-panel-sm, 10px);
|
|
748
|
+
box-shadow: 0 10px 32px rgba(0, 0, 0, 0.14);
|
|
749
|
+
}
|
|
750
|
+
.hitl-menu-title {
|
|
751
|
+
padding: 6px 10px;
|
|
752
|
+
font-size: 11px;
|
|
753
|
+
font-weight: 600;
|
|
754
|
+
letter-spacing: 0.08em;
|
|
755
|
+
color: var(--ink-faint);
|
|
756
|
+
border-bottom: 1px solid var(--line);
|
|
757
|
+
}
|
|
758
|
+
.hitl-item {
|
|
759
|
+
display: flex;
|
|
760
|
+
flex-direction: column;
|
|
761
|
+
gap: 3px;
|
|
762
|
+
width: 100%;
|
|
763
|
+
margin-top: 4px;
|
|
764
|
+
padding: 8px 10px;
|
|
765
|
+
text-align: left;
|
|
766
|
+
cursor: pointer;
|
|
767
|
+
background: transparent;
|
|
768
|
+
border: 1px solid transparent;
|
|
769
|
+
border-radius: var(--radius-panel-sm, 8px);
|
|
770
|
+
transition: background var(--transition-fast), border-color var(--transition-fast);
|
|
771
|
+
}
|
|
772
|
+
.hitl-item:hover {
|
|
773
|
+
background: var(--paper-deep);
|
|
774
|
+
border-color: var(--line);
|
|
775
|
+
}
|
|
776
|
+
.hitl-item-top {
|
|
777
|
+
display: flex;
|
|
778
|
+
gap: 8px;
|
|
779
|
+
align-items: baseline;
|
|
780
|
+
justify-content: space-between;
|
|
781
|
+
}
|
|
782
|
+
.hitl-item-agent {
|
|
783
|
+
font-size: 12.5px;
|
|
784
|
+
font-weight: 600;
|
|
785
|
+
color: var(--ink);
|
|
786
|
+
}
|
|
787
|
+
.hitl-item-kind {
|
|
788
|
+
font-family: var(--font-mono);
|
|
789
|
+
font-size: 9.5px;
|
|
790
|
+
letter-spacing: 0.1em;
|
|
791
|
+
color: var(--tone-warning-dot, #d4a017);
|
|
792
|
+
}
|
|
793
|
+
.hitl-item-title {
|
|
794
|
+
overflow: hidden;
|
|
795
|
+
font-size: 12px;
|
|
796
|
+
color: var(--ink-soft);
|
|
797
|
+
text-overflow: ellipsis;
|
|
798
|
+
white-space: nowrap;
|
|
799
|
+
}
|
|
800
|
+
|
|
663
801
|
@media (prefers-reduced-motion: reduce) {
|
|
664
802
|
.plotter-line.run {
|
|
665
803
|
animation: none;
|
|
@@ -8,6 +8,7 @@ import { useUserStore } from '../../stores/workshop/user'
|
|
|
8
8
|
import { WorkshopWsSession, useWsConnectionStore } from '../../stores/workshop/connection'
|
|
9
9
|
import { useEventsStore } from '../../stores/workshop/events'
|
|
10
10
|
import { useEntitiesStore } from '../../stores/workshop/entities'
|
|
11
|
+
import { useHitlStore } from '../../stores/workshop/hitl'
|
|
11
12
|
import { useTownBus } from './useTownBus'
|
|
12
13
|
import type { AepEnvelope, AepSnapshot } from '#shared/workshop-protocol'
|
|
13
14
|
|
|
@@ -16,6 +17,7 @@ export function useWorkshopWs() {
|
|
|
16
17
|
const userStore = useUserStore()
|
|
17
18
|
const events = useEventsStore()
|
|
18
19
|
const entities = useEntitiesStore()
|
|
20
|
+
const hitl = useHitlStore()
|
|
19
21
|
const townBus = useTownBus()
|
|
20
22
|
|
|
21
23
|
// 模块级单例:多组件共享一条连接
|
|
@@ -48,6 +50,8 @@ export function useWorkshopWs() {
|
|
|
48
50
|
events.ingest(e)
|
|
49
51
|
entities.applyEvent(e)
|
|
50
52
|
}
|
|
53
|
+
// HITL 全局待办(频道帧 + channelId='' 全员直推各到一次,store 内 kind+id 幂等)
|
|
54
|
+
if (e.type === 'hitl.request' || e.type === 'hitl.resolved') hitl.applyEnvelope(e)
|
|
51
55
|
if (typeof e.seq === 'number' && e.seq > 0 && e.channelId) {
|
|
52
56
|
conn.cursors[e.channelId] = e.seq
|
|
53
57
|
s.updateCursor(e.channelId, e.seq) // 推进重连续传游标
|
package/app/pages/monitor.vue
CHANGED
|
@@ -67,6 +67,7 @@ interface MonitorSnapshot {
|
|
|
67
67
|
interface ApiEnvelope<T> { code: number | string, message: string, data: T | null }
|
|
68
68
|
|
|
69
69
|
const { t } = useI18n()
|
|
70
|
+
const route = useRoute()
|
|
70
71
|
const userStore = useUserStore()
|
|
71
72
|
const isAdmin = computed(() => userStore.isAdmin)
|
|
72
73
|
|
|
@@ -112,7 +113,9 @@ watch(() => userStore.token, () => {
|
|
|
112
113
|
})
|
|
113
114
|
|
|
114
115
|
onMounted(() => {
|
|
115
|
-
if (userStore.token)
|
|
116
|
+
if (userStore.token) {
|
|
117
|
+
void poll().then(() => openFromQuery())
|
|
118
|
+
}
|
|
116
119
|
applyTimer()
|
|
117
120
|
})
|
|
118
121
|
onBeforeUnmount(() => {
|
|
@@ -169,6 +172,19 @@ const openTerminal = (pid: number, name: string | null, role: string | null): vo
|
|
|
169
172
|
terminalOpen.value = true
|
|
170
173
|
}
|
|
171
174
|
|
|
175
|
+
// HITL 徽标跳转定位:?agentId=&channelId= → 自动打开该 agent 的终端面板(omp 未
|
|
176
|
+
// spawn 时无进程行,保持关闭;用后即清 query,刷新/再进不重复弹开)
|
|
177
|
+
const openFromQuery = (): void => {
|
|
178
|
+
const agentId = route.query.agentId
|
|
179
|
+
if (typeof agentId !== 'string' || !agentId) return
|
|
180
|
+
const channelId = typeof route.query.channelId === 'string' ? route.query.channelId : ''
|
|
181
|
+
const proc = snapshot.value?.processes.find(p => p.agentId === agentId && (!channelId || p.channelId === channelId))
|
|
182
|
+
if (proc?.pid) {
|
|
183
|
+
openTerminal(proc.pid, proc.name, proc.role)
|
|
184
|
+
void navigateTo({ path: '/monitor' }, { replace: true })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
// ===== 展示辅助 =====
|
|
173
189
|
const shortId = (id: string | null | undefined): string => (id && id.length > 8 ? `${id.slice(0, 8)}…` : (id ?? '-'))
|
|
174
190
|
const stateColor: Record<string, string> = { idle: 'success', busy: 'processing', stopped: 'error' }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITL 全局待办 store:omp ask 对话框 + dcw 工具审批的统一待答视图。
|
|
3
|
+
*
|
|
4
|
+
* 数据源:
|
|
5
|
+
* - AEP hitl.request/hitl.resolved 帧(频道流 seq 帧与 channelId='' 全员直推
|
|
6
|
+
* 会各到一次,upsert 按 kind+id 幂等去重;徽标不依赖 channel 订阅)
|
|
7
|
+
* - GET /api/workshop/hitl/pending 快照(页面挂载/重连后的基线对齐)
|
|
8
|
+
*/
|
|
9
|
+
import { defineStore } from 'pinia'
|
|
10
|
+
import type { AepEnvelope, AepHitlItem } from '#shared/workshop-protocol'
|
|
11
|
+
import { useUserStore } from './user'
|
|
12
|
+
|
|
13
|
+
const keyOf = (i: Pick<AepHitlItem, 'kind' | 'id'>) => `${i.kind}:${i.id}`
|
|
14
|
+
|
|
15
|
+
interface HitlPendingRes {
|
|
16
|
+
code: number | string
|
|
17
|
+
data?: { items: AepHitlItem[] }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useHitlStore = defineStore('workshop.hitl', {
|
|
21
|
+
state: () => ({
|
|
22
|
+
/** 当前待人工处理条目(kind+id 幂等) */
|
|
23
|
+
items: [] as AepHitlItem[],
|
|
24
|
+
/** 快照已加载(防重复拉取;WS 掉线重连后由 ensureBaseline 重置) */
|
|
25
|
+
snapshotLoaded: false,
|
|
26
|
+
}),
|
|
27
|
+
getters: {
|
|
28
|
+
count: state => state.items.length,
|
|
29
|
+
/** 按 agent 分组(徽标下拉/跳转用) */
|
|
30
|
+
byAgent: (state) => {
|
|
31
|
+
const map = new Map<string, AepHitlItem[]>()
|
|
32
|
+
for (const i of state.items) {
|
|
33
|
+
const list = map.get(i.agentId) ?? []
|
|
34
|
+
list.push(i)
|
|
35
|
+
map.set(i.agentId, list)
|
|
36
|
+
}
|
|
37
|
+
return map
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
actions: {
|
|
41
|
+
/** AEP 帧消费(hitl.request / hitl.resolved;幂等 upsert/remove) */
|
|
42
|
+
applyEnvelope(e: AepEnvelope): void {
|
|
43
|
+
if (e.type === 'hitl.request') {
|
|
44
|
+
const item = e.payload as AepHitlItem
|
|
45
|
+
const key = keyOf(item)
|
|
46
|
+
const idx = this.items.findIndex(i => keyOf(i) === key)
|
|
47
|
+
if (idx >= 0) this.items[idx] = item
|
|
48
|
+
else this.items.push(item)
|
|
49
|
+
}
|
|
50
|
+
else if (e.type === 'hitl.resolved') {
|
|
51
|
+
const r = e.payload as { kind: AepHitlItem['kind'], id: string }
|
|
52
|
+
this.items = this.items.filter(i => keyOf(i) !== keyOf(r))
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
/** 快照对齐(挂载/重连;替换式 —— 以 REST 为准收敛 WS 期间的可能漂移) */
|
|
56
|
+
async loadSnapshot(): Promise<void> {
|
|
57
|
+
if (typeof window === 'undefined') return
|
|
58
|
+
const token = useUserStore().token
|
|
59
|
+
if (!token) return
|
|
60
|
+
try {
|
|
61
|
+
const res = await $fetch<HitlPendingRes>('/api/workshop/hitl/pending', {
|
|
62
|
+
headers: { authorization: `Bearer ${token}` },
|
|
63
|
+
})
|
|
64
|
+
if (res.code === 0 && res.data) {
|
|
65
|
+
this.items = res.data.items
|
|
66
|
+
this.snapshotLoaded = true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { /* 快照失败不阻塞实时帧 */ }
|
|
70
|
+
},
|
|
71
|
+
/** WS 断线重连后重新对齐 */
|
|
72
|
+
invalidate(): void {
|
|
73
|
+
this.snapshotLoaded = false
|
|
74
|
+
},
|
|
75
|
+
clear(): void {
|
|
76
|
+
this.items = []
|
|
77
|
+
this.snapshotLoaded = false
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
})
|
package/bin/aw.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 指令:stop —— 终止当前配置根下运行中的 aw 实例(读单实例锁 → 终止进程树)。
|
|
3
|
+
// 用法:aw stop [--home] (--home 强制以 home 配置根为目标;默认随 cwd 解析)
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { spawnSync } from 'node:child_process'
|
|
8
|
+
import { color } from '../core/logger.mjs'
|
|
9
|
+
|
|
10
|
+
export const meta = {
|
|
11
|
+
name: 'stop',
|
|
12
|
+
aliases: [],
|
|
13
|
+
group: '服务',
|
|
14
|
+
summary: '终止运行中的 aw 服务实例(读单实例锁)',
|
|
15
|
+
usage: 'aw stop [--home]',
|
|
16
|
+
needsProject: false,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function pidAlive(pid) {
|
|
20
|
+
try {
|
|
21
|
+
process.kill(pid, 0)
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return err?.code === 'EPERM'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 终止进程树:Windows taskkill /T /F;POSIX 先 SIGTERM 宽限 5s 再 SIGKILL */
|
|
30
|
+
function killTree(pid) {
|
|
31
|
+
if (process.platform === 'win32') {
|
|
32
|
+
const r = spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { encoding: 'utf8' })
|
|
33
|
+
if (r.status !== 0) throw new Error((r.stderr || r.stdout || 'taskkill 失败').trim().split(/\r?\n/)[0])
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 'SIGTERM')
|
|
38
|
+
}
|
|
39
|
+
catch { /* 已死 */ }
|
|
40
|
+
const deadline = Date.now() + 5000
|
|
41
|
+
while (Date.now() < deadline && pidAlive(pid)) {
|
|
42
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200)
|
|
43
|
+
}
|
|
44
|
+
if (pidAlive(pid)) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 'SIGKILL')
|
|
47
|
+
}
|
|
48
|
+
catch { /* 已死 */ }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function run(argv, ctx) {
|
|
53
|
+
// argv 为 parseArgs 结果({ flags, positionals, unknown });兼容裸数组调用
|
|
54
|
+
const flags = argv?.flags ?? {}
|
|
55
|
+
const positionals = Array.isArray(argv?.positionals) ? argv.positionals : (Array.isArray(argv) ? argv : [])
|
|
56
|
+
const forceHome = flags.home === true || positionals.includes('--home')
|
|
57
|
+
|
|
58
|
+
const configRoot = forceHome
|
|
59
|
+
? ctx.home
|
|
60
|
+
: (ctx.mode === 'repo' ? join(ctx.root, '.AgentWorkShop') : ctx.home)
|
|
61
|
+
const lockPath = join(configRoot, '.runtime', 'aw.lock')
|
|
62
|
+
|
|
63
|
+
if (!existsSync(lockPath)) {
|
|
64
|
+
console.log(`${color.blue('ℹ')} 未发现运行中的实例(无锁文件:${lockPath})`)
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
let holder
|
|
68
|
+
try {
|
|
69
|
+
holder = JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
rmSync(lockPath, { force: true })
|
|
73
|
+
console.log(`${color.blue('ℹ')} 锁文件损坏,已清除:${lockPath}`)
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pid = Number(holder?.pid)
|
|
78
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
79
|
+
rmSync(lockPath, { force: true })
|
|
80
|
+
console.log(`${color.blue('ℹ')} 锁内 PID 无效,已清除锁文件`)
|
|
81
|
+
return 0
|
|
82
|
+
}
|
|
83
|
+
if (!pidAlive(pid)) {
|
|
84
|
+
rmSync(lockPath, { force: true })
|
|
85
|
+
console.log(`${color.blue('ℹ')} 持锁进程已不存在(pid=${pid},残留锁),已清除锁文件`)
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`› 终止实例 pid=${pid}(mode=${holder.mode ?? '?'}${holder.port ? `,端口=${holder.port}` : ''},启动于 ${holder.startedAt ?? '?'})`)
|
|
90
|
+
try {
|
|
91
|
+
killTree(pid)
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error(`${color.red('✖')} 终止失败:${err.message}`)
|
|
95
|
+
console.error(` 可手动执行:taskkill /PID ${pid} /T /F(Windows)`)
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
// 锁文件由退出钩子自清;残留则手动兜底
|
|
99
|
+
for (let i = 0; i < 20 && existsSync(lockPath); i++) {
|
|
100
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200)
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
if (existsSync(lockPath)) rmSync(lockPath, { force: true })
|
|
104
|
+
}
|
|
105
|
+
catch { /* ignore */ }
|
|
106
|
+
console.log(`${color.green('✔')} 实例已停止(pid=${pid})`)
|
|
107
|
+
return 0
|
|
108
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 指令:tui —— 终端工作台(与 AgentTeam 交互作业;实现在 tui/)。
|
|
3
|
+
// 用法:aw tui [--url http://127.0.0.1:3000] [--token ut-*] [--channel <名>]
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
export const meta = {
|
|
7
|
+
name: 'tui',
|
|
8
|
+
aliases: ['tui'],
|
|
9
|
+
group: '交互',
|
|
10
|
+
summary: '终端工作台 TUI:频道管理/发任务/实时监控/HITL 作答',
|
|
11
|
+
usage: 'aw tui [--url <baseUrl>] [--token <ut-*>] [--channel <名>]',
|
|
12
|
+
needsProject: false,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function run(argv) {
|
|
16
|
+
const { main } = await import('../../tui/aw-tui.mjs')
|
|
17
|
+
await main(argv)
|
|
18
|
+
return 0
|
|
19
|
+
}
|