jorgex-stack 1.9.74 → 1.9.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -11
- package/dist/browser-playwright.js +22 -10
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +14608 -11361
- package/package.json +1 -1
- package/stack/assets/opencode/sounds/done.wav +0 -0
- package/stack/assets/opencode/sounds/silent.wav +0 -0
- package/stack/assets/opencode/tui/subagents/state.mjs +37 -0
- package/stack/assets/opencode/tui/subagents/tui.tsx +145 -0
- package/upstreams.json +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jorgex-stack",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.75",
|
|
4
4
|
"description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI, OpenCode y Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function subagentState(status, outcome, blocked = false) {
|
|
2
|
+
if (blocked) return "blocked"
|
|
3
|
+
if (status === "running") return "running"
|
|
4
|
+
if (outcome === "succeeded") return "done"
|
|
5
|
+
if (outcome === "failed") return "failed"
|
|
6
|
+
if (outcome === "interrupted") return "stopped"
|
|
7
|
+
return "idle"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isActiveSubagent(state) {
|
|
11
|
+
return state === "running" || state === "blocked"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function activeElapsed(state, startedAt, now) {
|
|
15
|
+
if (!isActiveSubagent(state) || !Number.isFinite(startedAt)) return undefined
|
|
16
|
+
const seconds = Math.max(0, Math.floor((now - startedAt) / 1000))
|
|
17
|
+
const minutes = Math.floor(seconds / 60) % 60
|
|
18
|
+
const parts = [minutes, seconds % 60].map((value) => String(value).padStart(2, "0"))
|
|
19
|
+
if (seconds >= 3600) parts.unshift(String(Math.floor(seconds / 3600)).padStart(2, "0"))
|
|
20
|
+
return parts.join(":")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatTokens(tokens) {
|
|
24
|
+
if (!tokens) return undefined
|
|
25
|
+
const buckets = [tokens.input, tokens.output, tokens.reasoning, tokens.cache?.read, tokens.cache?.write]
|
|
26
|
+
if (buckets.some((value) => !Number.isFinite(value) || value < 0)) return undefined
|
|
27
|
+
const total = buckets.reduce((sum, value) => sum + value, 0)
|
|
28
|
+
if (!Number.isFinite(total)) return undefined
|
|
29
|
+
if (total >= 1000000) return `${(total / 1000000).toFixed(1)}m tok`
|
|
30
|
+
if (total >= 1000) return `${(total / 1000).toFixed(1)}k tok`
|
|
31
|
+
return `${Math.round(total)} tok`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatModel(model) {
|
|
35
|
+
if (!model?.providerID || !model?.id) return undefined
|
|
36
|
+
return `${model.providerID}/${model.id}${model.variant ? `#${model.variant}` : ""}`
|
|
37
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// OpenCode v2 CLI sidebar plugin, registered as "./tui/subagents" in cli.json.
|
|
2
|
+
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from "solid-js"
|
|
3
|
+
import { Plugin } from "@opencode/plugin/tui"
|
|
4
|
+
import { activeElapsed, formatModel, formatTokens, isActiveSubagent, subagentState } from "./state.mjs"
|
|
5
|
+
|
|
6
|
+
export default Plugin.define({
|
|
7
|
+
id: "jorgex.subagents",
|
|
8
|
+
setup(context) {
|
|
9
|
+
const [open, setOpen] = createSignal(false)
|
|
10
|
+
const toggle = () => { setOpen((value) => !value) }
|
|
11
|
+
const [starts, setStarts] = createSignal<Record<string, number>>({})
|
|
12
|
+
// Do not infer execution duration from session creation: sessions can run again.
|
|
13
|
+
const stopStarted = context.data.on("session.execution.started", (event) => {
|
|
14
|
+
setStarts((current) => ({ ...current, [event.data.sessionID]: event.created }))
|
|
15
|
+
})
|
|
16
|
+
const stopEnded = context.data.listen(({ details }) => {
|
|
17
|
+
if (!["session.execution.succeeded", "session.execution.failed", "session.execution.interrupted", "session.deleted"].includes(details.type)) return
|
|
18
|
+
const id = details.data.sessionID
|
|
19
|
+
if (!id) return
|
|
20
|
+
setStarts((current) => {
|
|
21
|
+
const next = { ...current }
|
|
22
|
+
delete next[id]
|
|
23
|
+
return next
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
const theme = () => context.theme
|
|
27
|
+
const state = (id: string) => subagentState(
|
|
28
|
+
context.data.session.status(id),
|
|
29
|
+
context.data.session.get(id)?.outcome,
|
|
30
|
+
(context.data.session.permission.list(id) ?? []).length > 0 ||
|
|
31
|
+
(context.data.session.form.list(id) ?? []).length > 0,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Keymap layers require the keymap provider, which is not available during setup.
|
|
35
|
+
const unregisterCommands = context.ui.slot({
|
|
36
|
+
append: "app",
|
|
37
|
+
render: () => {
|
|
38
|
+
context.keymap.layer(() => ({
|
|
39
|
+
mode: "global",
|
|
40
|
+
commands: [{
|
|
41
|
+
id: "jorgex.subagents.toggle",
|
|
42
|
+
title: "Toggle subagent panel",
|
|
43
|
+
group: "Subagents",
|
|
44
|
+
palette: true,
|
|
45
|
+
slash: { name: "subagents" },
|
|
46
|
+
run: toggle,
|
|
47
|
+
}],
|
|
48
|
+
}))
|
|
49
|
+
return null
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const Card = (props: { sessionID: string }) => {
|
|
54
|
+
createEffect(on(() => props.sessionID, () => setOpen(false)))
|
|
55
|
+
const [now, setNow] = createSignal(Date.now())
|
|
56
|
+
const children = createMemo(() => {
|
|
57
|
+
const root = context.data.session.root(props.sessionID) ?? props.sessionID
|
|
58
|
+
return context.data.session.family(root).filter((id) => id !== root)
|
|
59
|
+
})
|
|
60
|
+
const active = createMemo(() => children().filter((id) => isActiveSubagent(state(id))))
|
|
61
|
+
createEffect(() => {
|
|
62
|
+
if (!open() || active().length === 0) return
|
|
63
|
+
setNow(Date.now())
|
|
64
|
+
const timer = setInterval(() => setNow(Date.now()), 1000)
|
|
65
|
+
onCleanup(() => clearInterval(timer))
|
|
66
|
+
})
|
|
67
|
+
const counts = createMemo(() => {
|
|
68
|
+
const result = { running: 0, blocked: 0, done: 0 }
|
|
69
|
+
for (const id of children()) {
|
|
70
|
+
const value = state(id)
|
|
71
|
+
if (value === "running" || value === "blocked" || value === "done") result[value]++
|
|
72
|
+
}
|
|
73
|
+
return result
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<box flexDirection="column" gap={0}>
|
|
78
|
+
<box flexDirection="column" onMouseDown={(event) => {
|
|
79
|
+
if (event.button !== 0) return
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
event.stopPropagation()
|
|
82
|
+
toggle()
|
|
83
|
+
}}>
|
|
84
|
+
<text fg={theme().text.base} selectable={false}>
|
|
85
|
+
<b>{`${open() ? "▼" : "▶"} Subagents`}</b>
|
|
86
|
+
</text>
|
|
87
|
+
<Show when={children().length > 0} fallback={<text fg={theme().text.muted}>No subagents</text>}>
|
|
88
|
+
<text selectable={false}>
|
|
89
|
+
<span style={{ fg: theme().text.feedback.warning.base }}>{`• ${counts().running} running`}</span>
|
|
90
|
+
<Show when={counts().blocked > 0}>
|
|
91
|
+
<span style={{ fg: theme().text.feedback.warning.base }}>{` · ${counts().blocked} needs input`}</span>
|
|
92
|
+
</Show>
|
|
93
|
+
<span style={{ fg: theme().text.feedback.success.base }}>{` · ✓ ${counts().done} done`}</span>
|
|
94
|
+
</text>
|
|
95
|
+
</Show>
|
|
96
|
+
</box>
|
|
97
|
+
<Show when={open()}>
|
|
98
|
+
<Show when={active().length > 0} fallback={<text fg={theme().text.muted}>No active subagents</text>}>
|
|
99
|
+
<For each={active()}>
|
|
100
|
+
{(id) => {
|
|
101
|
+
const info = () => context.data.session.get(id)
|
|
102
|
+
const duration = () => activeElapsed(state(id), starts()[id], now())
|
|
103
|
+
const metrics = () => [duration(), formatTokens(info()?.tokens)].filter(Boolean).join(" · ")
|
|
104
|
+
const model = () => formatModel(info()?.model)
|
|
105
|
+
return (
|
|
106
|
+
<box flexDirection="column" paddingLeft={1} onMouseUp={(event) => {
|
|
107
|
+
if (event.button !== 0) return
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
event.stopPropagation()
|
|
110
|
+
context.ui.router.navigate({ type: "session", sessionID: id })
|
|
111
|
+
}}>
|
|
112
|
+
<box flexDirection="row" gap={1} minWidth={0}>
|
|
113
|
+
<text fg={theme().text.feedback.warning.base} flexShrink={0} selectable={false}>•</text>
|
|
114
|
+
<text fg={theme().text.base} flexGrow={1} minWidth={0} wrapMode="none" truncate selectable={false}>
|
|
115
|
+
<b>{info()?.agent ?? info()?.title ?? id}</b>
|
|
116
|
+
</text>
|
|
117
|
+
<Show when={state(id) === "blocked"}>
|
|
118
|
+
<text fg={theme().text.feedback.warning.base} flexShrink={0} selectable={false}>Needs input</text>
|
|
119
|
+
</Show>
|
|
120
|
+
</box>
|
|
121
|
+
<Show when={metrics()}>
|
|
122
|
+
<text fg={theme().text.muted} paddingLeft={2} wrapMode="none" truncate selectable={false}>{metrics()}</text>
|
|
123
|
+
</Show>
|
|
124
|
+
<Show when={model()}>
|
|
125
|
+
<text fg={theme().text.muted} paddingLeft={2} wrapMode="none" truncate selectable={false}>
|
|
126
|
+
{model()}
|
|
127
|
+
</text>
|
|
128
|
+
</Show>
|
|
129
|
+
</box>
|
|
130
|
+
)
|
|
131
|
+
}}
|
|
132
|
+
</For>
|
|
133
|
+
</Show>
|
|
134
|
+
</Show>
|
|
135
|
+
</box>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const unregister = context.ui.slot({
|
|
140
|
+
append: "sidebar.content",
|
|
141
|
+
render: ({ sessionID }) => <Card sessionID={sessionID} />,
|
|
142
|
+
})
|
|
143
|
+
return () => { unregister(); unregisterCommands(); stopStarted(); stopEnded() }
|
|
144
|
+
},
|
|
145
|
+
})
|
package/upstreams.json
CHANGED
|
@@ -59,7 +59,14 @@
|
|
|
59
59
|
"version": null,
|
|
60
60
|
"strategy": "provider-managed",
|
|
61
61
|
"reviewed": "2026-09-24",
|
|
62
|
-
"note": "
|
|
62
|
+
"note": "Árbol Stack gestionado y verificado con guard (no un CLI global compartido); opt-in para los runtimes distintos de OpenCode; OpenCode v2 usa Browser Control obligatorio (sin selector Playwright CLI). provider-managed sin re-pin manual ni selector fijo futuro"
|
|
63
|
+
},
|
|
64
|
+
"browser-control": {
|
|
65
|
+
"source": "npm:@opencode-ai/browser-control",
|
|
66
|
+
"version": null,
|
|
67
|
+
"strategy": "provider-managed",
|
|
68
|
+
"reviewed": "2026-10-02",
|
|
69
|
+
"note": "CLI+skill+MCP oficiales; provider gestiona la versión (rolling/latest) con SRI verificado y guard de ejecución; solo OpenCode v2. Último observado latest 0.8.3 (SRI sha512) como referencia informativa, sin fijarlo"
|
|
63
70
|
}
|
|
64
71
|
},
|
|
65
72
|
"skills": {
|