supipowers 0.7.9 → 0.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "0.7.9",
3
+ "version": "0.7.10",
4
4
  "description": "OMP-native workflow extension inspired by supipowers.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -95,10 +95,91 @@ async function executeSubAgent(
95
95
  task: PlanTask,
96
96
  config: SupipowersConfig,
97
97
  ): Promise<SubAgentResult> {
98
- throw new Error(
99
- "Sub-agent dispatch requires OMP runtime. " +
100
- "This will be connected to createAgentSession during integration.",
101
- );
98
+ const { createAgentSession } = pi.pi;
99
+
100
+ const { session } = await createAgentSession({
101
+ cwd: process.cwd(),
102
+ hasUI: false,
103
+ taskDepth: 1,
104
+ parentTaskPrefix: `task-${task.id}`,
105
+ });
106
+
107
+ // Track files changed by monitoring tool calls
108
+ const filesChanged = new Set<string>();
109
+ const FILE_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
110
+ const pendingToolArgs = new Map<string, Record<string, unknown>>();
111
+ const unsubscribe = session.subscribe((event) => {
112
+ if (event.type === "tool_execution_start" && FILE_TOOLS.has(event.toolName)) {
113
+ if (event.args?.file_path) {
114
+ pendingToolArgs.set(event.toolCallId, event.args as Record<string, unknown>);
115
+ }
116
+ }
117
+ if (event.type === "tool_execution_end" && !event.isError) {
118
+ const args = pendingToolArgs.get(event.toolCallId);
119
+ if (args?.file_path) {
120
+ filesChanged.add(String(args.file_path));
121
+ }
122
+ pendingToolArgs.delete(event.toolCallId);
123
+ }
124
+ });
125
+
126
+ try {
127
+ await session.prompt(prompt, { expandPromptTemplates: false });
128
+
129
+ // Extract the last assistant message
130
+ const messages = session.state.messages;
131
+ const lastAssistant = [...messages]
132
+ .reverse()
133
+ .find((m) => m.role === "assistant");
134
+
135
+ const output = extractTextContent(lastAssistant?.content);
136
+ const status = parseAgentStatus(output);
137
+
138
+ return {
139
+ status,
140
+ output,
141
+ concerns: status === "done_with_concerns"
142
+ ? extractConcerns(output)
143
+ : undefined,
144
+ filesChanged: [...filesChanged],
145
+ };
146
+ } finally {
147
+ unsubscribe();
148
+ await session.dispose();
149
+ }
150
+ }
151
+
152
+ function extractTextContent(content: unknown): string {
153
+ if (!content) return "";
154
+ if (typeof content === "string") return content;
155
+ if (Array.isArray(content)) {
156
+ return content
157
+ .filter((block: { type?: string }) => block.type === "text")
158
+ .map((block: { text?: string }) => block.text ?? "")
159
+ .join("\n");
160
+ }
161
+ return String(content);
162
+ }
163
+
164
+ function parseAgentStatus(output: string): AgentStatus {
165
+ const upper = output.toUpperCase();
166
+ if (upper.includes("BLOCKED") || upper.includes("NEEDS_CONTEXT")) {
167
+ return "blocked";
168
+ }
169
+ if (upper.includes("DONE_WITH_CONCERNS")) {
170
+ return "done_with_concerns";
171
+ }
172
+ // "DONE" appears in both "DONE" and "DONE_WITH_CONCERNS", so check after
173
+ if (upper.includes("**STATUS:** DONE") || upper.includes("STATUS: DONE")) {
174
+ return "done";
175
+ }
176
+ // Default: if agent completed without explicit status, treat as done
177
+ return "done";
178
+ }
179
+
180
+ function extractConcerns(output: string): string {
181
+ const match = output.match(/(?:concerns?|issues?|worries):\s*(.+?)(?:\n\n|$)/is);
182
+ return match?.[1]?.trim() ?? "";
102
183
  }
103
184
 
104
185
  /** Review result from a spec compliance or code quality reviewer */