@hueyexe/opencode-ensemble 0.7.0 → 0.7.1
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 +1 -1
- package/dist/index.js +24 -3
- package/package.json +1 -1
- package/src/hooks.ts +14 -0
- package/src/index.ts +12 -1
- package/src/system-prompt.ts +9 -3
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -15675,6 +15675,13 @@ function checkToolIsolation(registry2, tracker, toolName, sessionId) {
|
|
|
15675
15675
|
throw new Error("Team tools are not available to sub-agents. Report findings to your parent teammate via your normal output.");
|
|
15676
15676
|
}
|
|
15677
15677
|
}
|
|
15678
|
+
function shouldNudgeIdleMember(db, teamId, memberName) {
|
|
15679
|
+
const member = db.query("SELECT status FROM team_member WHERE team_id = ? AND name = ?").get(teamId, memberName);
|
|
15680
|
+
if (!member || member.status !== "ready")
|
|
15681
|
+
return false;
|
|
15682
|
+
const msg = db.query("SELECT id FROM team_message WHERE team_id = ? AND from_name = ? AND (to_name = 'lead' OR to_name IS NULL) LIMIT 1").get(teamId, memberName);
|
|
15683
|
+
return !msg;
|
|
15684
|
+
}
|
|
15678
15685
|
|
|
15679
15686
|
// src/notify.ts
|
|
15680
15687
|
var TOAST_CONFIG = {
|
|
@@ -15784,7 +15791,7 @@ function buildTeammateSystemPrompt(db, teamId, memberName) {
|
|
|
15784
15791
|
const team = db.query("SELECT name FROM team WHERE id = ?").get(teamId);
|
|
15785
15792
|
if (!team)
|
|
15786
15793
|
return "";
|
|
15787
|
-
return `You are "${memberName}", a teammate in team "${team.name}". Use team_message to communicate.`;
|
|
15794
|
+
return `You are "${memberName}", a teammate in team "${team.name}". Use team_message to communicate. You MUST send your results to the lead via team_message before stopping.`;
|
|
15788
15795
|
}
|
|
15789
15796
|
function buildTeamCompactionContext(db, teamId, role, memberName) {
|
|
15790
15797
|
const team = db.query("SELECT name FROM team WHERE id = ?").get(teamId);
|
|
@@ -15802,11 +15809,15 @@ function buildTeamCompactionContext(db, teamId, role, memberName) {
|
|
|
15802
15809
|
const roleLine = role === "lead" ? `[Team Context] You are the lead of team "${team.name}".` : `[Team Context] You are a teammate named "${memberName}" in team "${team.name}".`;
|
|
15803
15810
|
const memberList = members.map((m) => `${m.name} (${STATUS_DISPLAY[m.status] ?? m.status})`).join(", ");
|
|
15804
15811
|
const membersLine = members.length > 0 ? `Members: ${memberList}` : "Members: none";
|
|
15805
|
-
|
|
15812
|
+
const lines = [
|
|
15806
15813
|
roleLine,
|
|
15807
15814
|
membersLine,
|
|
15808
15815
|
`Tasks: ${completed} completed, ${inProgress} in progress, ${pending} pending`
|
|
15809
|
-
]
|
|
15816
|
+
];
|
|
15817
|
+
if (role === "member") {
|
|
15818
|
+
lines.push("IMPORTANT: You MUST send your results to the lead via team_message before stopping.");
|
|
15819
|
+
}
|
|
15820
|
+
return lines.join(`
|
|
15810
15821
|
`);
|
|
15811
15822
|
}
|
|
15812
15823
|
|
|
@@ -16635,6 +16646,7 @@ var plugin = async (input) => {
|
|
|
16635
16646
|
const db = createDb(dbPath);
|
|
16636
16647
|
const registry2 = new MemberRegistry;
|
|
16637
16648
|
const tracker = new DescendantTracker;
|
|
16649
|
+
const nudgedMembers = new Set;
|
|
16638
16650
|
const pluginTransport = input.client._client;
|
|
16639
16651
|
const rawClient = new OpencodeClient({ client: pluginTransport });
|
|
16640
16652
|
initLog(rawClient);
|
|
@@ -16688,6 +16700,15 @@ var plugin = async (input) => {
|
|
|
16688
16700
|
notifyTeamEvent(client3, "shutdown", { memberName: transition.memberName });
|
|
16689
16701
|
} else if (transition.to === "ready" && transition.from === "busy") {
|
|
16690
16702
|
notifyTeamEvent(client3, "completed", { memberName: transition.memberName });
|
|
16703
|
+
const nudgeKey = `${transition.teamId}:${transition.memberName}`;
|
|
16704
|
+
if (!nudgedMembers.has(nudgeKey) && shouldNudgeIdleMember(db, transition.teamId, transition.memberName)) {
|
|
16705
|
+
nudgedMembers.add(nudgeKey);
|
|
16706
|
+
log(`nudge:idle-without-report name=${transition.memberName}`);
|
|
16707
|
+
client3.session.promptAsync({
|
|
16708
|
+
sessionID,
|
|
16709
|
+
parts: [{ type: "text", text: "[System]: You completed your work but did not report results. Send your findings to the lead via team_message now." }]
|
|
16710
|
+
}).catch(() => {});
|
|
16711
|
+
}
|
|
16691
16712
|
} else if (transition.to === "error") {
|
|
16692
16713
|
notifyTeamEvent(client3, "error", { memberName: transition.memberName });
|
|
16693
16714
|
} else if (transition.to === "retry") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hueyexe/opencode-ensemble",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Agent teams for OpenCode — parallel agents with peer-to-peer communication, shared tasks, and coordinated execution",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/hooks.ts
CHANGED
|
@@ -98,3 +98,17 @@ export function checkToolIsolation(
|
|
|
98
98
|
throw new Error("Team tools are not available to sub-agents. Report findings to your parent teammate via your normal output.")
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a member went idle without ever sending a message to the lead.
|
|
104
|
+
* Returns true if the member is idle/ready and has no outbound messages.
|
|
105
|
+
*/
|
|
106
|
+
export function shouldNudgeIdleMember(db: Database, teamId: string, memberName: string): boolean {
|
|
107
|
+
const member = db.query("SELECT status FROM team_member WHERE team_id = ? AND name = ?")
|
|
108
|
+
.get(teamId, memberName) as { status: string } | null
|
|
109
|
+
if (!member || member.status !== "ready") return false
|
|
110
|
+
|
|
111
|
+
const msg = db.query("SELECT id FROM team_message WHERE team_id = ? AND from_name = ? AND (to_name = 'lead' OR to_name IS NULL) LIMIT 1")
|
|
112
|
+
.get(teamId, memberName) as { id: string } | null
|
|
113
|
+
return !msg
|
|
114
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { wrapThrowingClient } from "./client"
|
|
|
8
8
|
import { recoverStaleMembers, recoverUndeliveredMessages, recoverOrphanedWorktrees } from "./recovery"
|
|
9
9
|
import { MemberRegistry, DescendantTracker } from "./state"
|
|
10
10
|
import { isWorktreeInstance } from "./util"
|
|
11
|
-
import { handleSessionStatusEvent, handleSessionCreatedEvent, checkToolIsolation } from "./hooks"
|
|
11
|
+
import { handleSessionStatusEvent, handleSessionCreatedEvent, checkToolIsolation, shouldNudgeIdleMember } from "./hooks"
|
|
12
12
|
import { notifyTeamEvent, notifyWorkingProgress } from "./notify"
|
|
13
13
|
import { buildLeadSystemPrompt, buildTeammateSystemPrompt, buildTeamCompactionContext } from "./system-prompt"
|
|
14
14
|
import { log, initLog } from "./log"
|
|
@@ -50,6 +50,7 @@ const plugin: Plugin = async (input) => {
|
|
|
50
50
|
// Initialize in-memory state
|
|
51
51
|
const registry = new MemberRegistry()
|
|
52
52
|
const tracker = new DescendantTracker()
|
|
53
|
+
const nudgedMembers = new Set<string>()
|
|
53
54
|
|
|
54
55
|
// Extract the working HeyAPI transport from the plugin-provided v1 client and pass it
|
|
55
56
|
// to the v2 OpencodeClient. The plugin framework provides a v1 client which stores its
|
|
@@ -123,6 +124,16 @@ const plugin: Plugin = async (input) => {
|
|
|
123
124
|
notifyTeamEvent(client, "shutdown", { memberName: transition.memberName })
|
|
124
125
|
} else if (transition.to === "ready" && transition.from === "busy") {
|
|
125
126
|
notifyTeamEvent(client, "completed", { memberName: transition.memberName })
|
|
127
|
+
// Nudge teammate if they went idle without reporting to the lead (once only)
|
|
128
|
+
const nudgeKey = `${transition.teamId}:${transition.memberName}`
|
|
129
|
+
if (!nudgedMembers.has(nudgeKey) && shouldNudgeIdleMember(db, transition.teamId, transition.memberName)) {
|
|
130
|
+
nudgedMembers.add(nudgeKey)
|
|
131
|
+
log(`nudge:idle-without-report name=${transition.memberName}`)
|
|
132
|
+
client.session.promptAsync({
|
|
133
|
+
sessionID,
|
|
134
|
+
parts: [{ type: "text", text: "[System]: You completed your work but did not report results. Send your findings to the lead via team_message now." }],
|
|
135
|
+
}).catch(() => { /* best effort */ })
|
|
136
|
+
}
|
|
126
137
|
} else if (transition.to === "error") {
|
|
127
138
|
notifyTeamEvent(client, "error", { memberName: transition.memberName })
|
|
128
139
|
} else if (transition.to === "retry") {
|
package/src/system-prompt.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function buildTeammateSystemPrompt(db: Database, teamId: string, memberNa
|
|
|
92
92
|
const team = db.query("SELECT name FROM team WHERE id = ?").get(teamId) as { name: string } | null
|
|
93
93
|
if (!team) return ""
|
|
94
94
|
|
|
95
|
-
return `You are "${memberName}", a teammate in team "${team.name}". Use team_message to communicate.`
|
|
95
|
+
return `You are "${memberName}", a teammate in team "${team.name}". Use team_message to communicate. You MUST send your results to the lead via team_message before stopping.`
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
@@ -135,9 +135,15 @@ export function buildTeamCompactionContext(
|
|
|
135
135
|
? `Members: ${memberList}`
|
|
136
136
|
: "Members: none"
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
const lines = [
|
|
139
139
|
roleLine,
|
|
140
140
|
membersLine,
|
|
141
141
|
`Tasks: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
|
|
142
|
-
]
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
if (role === "member") {
|
|
145
|
+
lines.push("IMPORTANT: You MUST send your results to the lead via team_message before stopping.")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return lines.join("\n")
|
|
143
149
|
}
|