ohmyvibe 0.1.4 → 0.1.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.
@@ -62,13 +62,33 @@ export function startHttpServer(sessionManager, port) {
62
62
  }
63
63
  });
64
64
  app.get("/api/sessions/:sessionId", (req, res) => {
65
- const session = sessionManager.get(req.params.sessionId);
65
+ const limit = typeof req.query.limit === "string" && Number.isFinite(Number(req.query.limit))
66
+ ? Number(req.query.limit)
67
+ : undefined;
68
+ const session = sessionManager.get(req.params.sessionId, { limit });
66
69
  if (!session) {
67
70
  res.status(404).json({ error: "Session not found" });
68
71
  return;
69
72
  }
70
73
  res.json(session);
71
74
  });
75
+ app.get("/api/sessions/:sessionId/transcript", (req, res) => {
76
+ try {
77
+ const beforeEntryId = typeof req.query.beforeEntryId === "string" ? req.query.beforeEntryId : undefined;
78
+ const limit = typeof req.query.limit === "string" && Number.isFinite(Number(req.query.limit))
79
+ ? Number(req.query.limit)
80
+ : undefined;
81
+ const page = sessionManager.getTranscriptPage(req.params.sessionId, { beforeEntryId, limit });
82
+ if (!page) {
83
+ res.status(404).json({ error: "Session not found" });
84
+ return;
85
+ }
86
+ res.json(page);
87
+ }
88
+ catch (error) {
89
+ res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
90
+ }
91
+ });
72
92
  app.post("/api/sessions/:sessionId/messages", async (req, res) => {
73
93
  try {
74
94
  const body = req.body;
@@ -1,12 +1,14 @@
1
1
  import "dotenv/config";
2
2
  import { ManagementBridge } from "./managementBridge.js";
3
+ import { ProjectManager } from "./projectManager.js";
3
4
  import { SessionManager } from "./sessionManager.js";
4
5
  const sessionManager = new SessionManager();
6
+ const projectManager = new ProjectManager(sessionManager);
5
7
  const managementServerUrl = process.env.MANAGEMENT_SERVER_URL;
6
8
  if (!managementServerUrl) {
7
9
  throw new Error("MANAGEMENT_SERVER_URL is required");
8
10
  }
9
- const bridge = new ManagementBridge(sessionManager, {
11
+ const bridge = new ManagementBridge(projectManager, {
10
12
  serverUrl: managementServerUrl,
11
13
  daemonId: process.env.DAEMON_ID,
12
14
  daemonName: process.env.DAEMON_NAME,
@@ -6,6 +6,7 @@ export class JsonRpcProcess extends EventEmitter {
6
6
  nextId = 1;
7
7
  stdoutBuffer = "";
8
8
  stderrBuffer = "";
9
+ finished = false;
9
10
  constructor(command, args, cwd) {
10
11
  super();
11
12
  this.child = spawn(command, args, {
@@ -17,15 +18,17 @@ export class JsonRpcProcess extends EventEmitter {
17
18
  this.child.stderr.setEncoding("utf8");
18
19
  this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
19
20
  this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
21
+ this.child.stdin.on("error", (error) => this.handleFailure(error));
22
+ this.child.stdout.on("error", (error) => this.handleFailure(error));
23
+ this.child.stderr.on("error", (error) => this.handleFailure(error));
20
24
  this.child.on("error", (error) => {
21
- for (const pending of this.pending.values()) {
22
- pending.reject(error);
23
- }
24
- this.pending.clear();
25
- this.emit("stderr", error.message);
26
- this.emit("exit", { code: null, signal: null });
25
+ this.handleFailure(error, { code: null, signal: null });
27
26
  });
28
27
  this.child.on("exit", (code, signal) => {
28
+ if (this.finished) {
29
+ return;
30
+ }
31
+ this.finished = true;
29
32
  this.flushStderr();
30
33
  const error = new Error(`JSON-RPC process exited (code=${code}, signal=${signal})`);
31
34
  for (const pending of this.pending.values()) {
@@ -133,4 +136,17 @@ export class JsonRpcProcess extends EventEmitter {
133
136
  this.emit("stderr", remaining);
134
137
  }
135
138
  }
139
+ handleFailure(error, exit = { code: null, signal: null }) {
140
+ if (this.finished) {
141
+ return;
142
+ }
143
+ this.finished = true;
144
+ for (const pending of this.pending.values()) {
145
+ pending.reject(error);
146
+ }
147
+ this.pending.clear();
148
+ this.flushStderr();
149
+ this.emit("stderr", error.message);
150
+ this.emit("exit", exit);
151
+ }
136
152
  }
@@ -6,20 +6,20 @@ export class ManagementBridge {
6
6
  serverUrl;
7
7
  daemonId;
8
8
  daemonName;
9
- sessionManager;
9
+ projectManager;
10
10
  socket;
11
11
  reconnectTimer;
12
12
  eventFlushTimer;
13
13
  pendingEvents = [];
14
- constructor(sessionManager, options) {
15
- this.sessionManager = sessionManager;
14
+ constructor(projectManager, options) {
15
+ this.projectManager = projectManager;
16
16
  this.serverUrl = options.serverUrl;
17
17
  this.daemonId = options.daemonId ?? randomUUID();
18
18
  this.daemonName = options.daemonName ?? os.hostname();
19
19
  }
20
20
  start() {
21
21
  this.connect();
22
- this.sessionManager.on("event", (event) => {
22
+ this.projectManager.on("event", (event) => {
23
23
  this.enqueueEvent(event);
24
24
  });
25
25
  }
@@ -94,9 +94,11 @@ export class ManagementBridge {
94
94
  connectedAt: new Date().toISOString(),
95
95
  lastSeenAt: new Date().toISOString(),
96
96
  online: true,
97
- sessionCount: this.sessionManager.list().length,
97
+ sessionCount: this.projectManager.listSessionsCached().length,
98
98
  },
99
- sessions: this.sessionManager.list(),
99
+ sessions: this.projectManager.listSessionsCached(),
100
+ projects: this.projectManager.listProjects(),
101
+ agents: this.projectManager.listAgents(),
100
102
  });
101
103
  }
102
104
  enqueueEvent(event) {
@@ -123,23 +125,38 @@ export class ManagementBridge {
123
125
  async handleRequest(method, params) {
124
126
  switch (method) {
125
127
  case "getConfig":
126
- return this.sessionManager.getConfig();
128
+ return this.projectManager.getConfig();
127
129
  case "listSessions":
128
- return this.sessionManager.list();
130
+ return this.projectManager.listSessions();
129
131
  case "getSession":
130
- return this.sessionManager.get(String(params.sessionId));
132
+ return this.projectManager.getSession(String(params.sessionId), {
133
+ limit: typeof params.limit === "number" ? params.limit : undefined,
134
+ });
135
+ case "getSessionTranscript":
136
+ return this.projectManager.getTranscriptPage(String(params.sessionId), {
137
+ beforeEntryId: typeof params.beforeEntryId === "string" ? params.beforeEntryId : undefined,
138
+ limit: typeof params.limit === "number" ? params.limit : undefined,
139
+ });
131
140
  case "listHistory":
132
- return this.sessionManager.listHistory();
141
+ return this.projectManager.listHistory();
133
142
  case "browseDirectories":
134
- return this.sessionManager.browseDirectories(typeof params.path === "string" ? params.path : undefined);
143
+ return this.projectManager.browseDirectories(typeof params.path === "string" ? params.path : undefined);
135
144
  case "browseSessionFiles":
136
- return this.sessionManager.browseSessionFiles(String(params.sessionId), typeof params.path === "string" ? params.path : undefined);
145
+ return this.projectManager.browseSessionFiles(String(params.sessionId), typeof params.path === "string" ? params.path : undefined);
137
146
  case "readSessionFile":
138
- return this.sessionManager.readSessionFile(String(params.sessionId), String(params.path));
147
+ return this.projectManager.readSessionFile(String(params.sessionId), String(params.path));
139
148
  case "writeSessionFile":
140
- return this.sessionManager.writeSessionFile(String(params.sessionId), String(params.path), String(params.content ?? ""));
149
+ return this.projectManager.writeSessionFile(String(params.sessionId), String(params.path), String(params.content ?? ""));
141
150
  case "createSession":
142
- return this.sessionManager.create({
151
+ return this.projectManager.createSession({
152
+ cwd: String(params.cwd ?? process.cwd()),
153
+ model: typeof params.model === "string" ? params.model : undefined,
154
+ reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
155
+ sandbox: typeof params.sandbox === "string" ? params.sandbox : undefined,
156
+ approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
157
+ });
158
+ case "createProjectSession":
159
+ return this.projectManager.createProjectSession(String(params.projectId), {
143
160
  cwd: String(params.cwd ?? process.cwd()),
144
161
  model: typeof params.model === "string" ? params.model : undefined,
145
162
  reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
@@ -147,7 +164,7 @@ export class ManagementBridge {
147
164
  approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
148
165
  });
149
166
  case "restoreSession":
150
- return this.sessionManager.restore({
167
+ return this.projectManager.restoreSession({
151
168
  threadId: String(params.threadId),
152
169
  cwd: typeof params.cwd === "string" ? params.cwd : undefined,
153
170
  model: typeof params.model === "string" ? params.model : undefined,
@@ -156,27 +173,65 @@ export class ManagementBridge {
156
173
  approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
157
174
  });
158
175
  case "sendMessage":
159
- await this.sessionManager.sendMessage(String(params.sessionId), String(params.text ?? ""));
176
+ await this.projectManager.sendMessage(String(params.sessionId), String(params.text ?? ""));
160
177
  return { ok: true };
161
178
  case "updateSessionConfig":
162
- return this.sessionManager.updateConfig(String(params.sessionId), {
179
+ return this.projectManager.updateSessionConfig(String(params.sessionId), {
163
180
  model: typeof params.model === "string" ? params.model : undefined,
164
181
  reasoningEffort: typeof params.reasoningEffort === "string" ? params.reasoningEffort : undefined,
165
182
  sandbox: typeof params.sandbox === "string" ? params.sandbox : undefined,
166
183
  approvalPolicy: typeof params.approvalPolicy === "string" ? params.approvalPolicy : undefined,
167
184
  });
168
185
  case "renameSession":
169
- return this.sessionManager.rename(String(params.sessionId), {
186
+ return this.projectManager.renameSession(String(params.sessionId), {
170
187
  title: String(params.title ?? ""),
171
188
  });
172
189
  case "interruptSession":
173
- await this.sessionManager.interrupt(String(params.sessionId));
190
+ await this.projectManager.interruptSession(String(params.sessionId));
174
191
  return { ok: true };
175
192
  case "respondApproval":
176
- return this.sessionManager.respondApproval(String(params.sessionId), String(params.approvalRequestId), params.decision === "deny" ? "deny" : "approve");
193
+ return this.projectManager.respondApproval(String(params.sessionId), String(params.approvalRequestId), params.decision === "deny" ? "deny" : "approve");
177
194
  case "closeSession":
178
- await this.sessionManager.close(String(params.sessionId));
195
+ await this.projectManager.closeSession(String(params.sessionId));
179
196
  return { ok: true };
197
+ case "listProjects":
198
+ return this.projectManager.listProjects();
199
+ case "getProject":
200
+ return this.projectManager.getProject(String(params.projectId));
201
+ case "createProject":
202
+ return this.projectManager.createProject({
203
+ name: String(params.name ?? ""),
204
+ rootDir: String(params.rootDir ?? process.cwd()),
205
+ goal: typeof params.goal === "string" ? params.goal : undefined,
206
+ runPolicy: params.runPolicy,
207
+ });
208
+ case "updateProject":
209
+ return this.projectManager.updateProject(String(params.projectId), {
210
+ name: typeof params.name === "string" ? params.name : undefined,
211
+ goal: typeof params.goal === "string" ? params.goal : undefined,
212
+ status: typeof params.status === "string" ? params.status : undefined,
213
+ runPolicy: params.runPolicy,
214
+ });
215
+ case "listAgents":
216
+ return this.projectManager.listAgents(typeof params.projectId === "string" ? params.projectId : undefined);
217
+ case "getAgent":
218
+ return this.projectManager.getAgent(String(params.agentId));
219
+ case "sendAgentMessage":
220
+ return this.projectManager.sendAgentMessage(String(params.projectId), String(params.agentId), String(params.text ?? ""));
221
+ case "clearAgentLogs":
222
+ return this.projectManager.clearAgentLogs(String(params.projectId), String(params.agentId));
223
+ case "runProject":
224
+ return this.projectManager.runProject(String(params.projectId));
225
+ case "pauseProject":
226
+ return this.projectManager.pauseProject(String(params.projectId));
227
+ case "getSettings":
228
+ return this.projectManager.getSettings();
229
+ case "updateProviderConfig":
230
+ return this.projectManager.updateProviderConfig(params);
231
+ case "updateNotificationConfig":
232
+ return this.projectManager.updateNotificationConfig(params);
233
+ case "listNotifications":
234
+ return this.projectManager.listNotifications(typeof params.projectId === "string" ? params.projectId : undefined);
180
235
  default:
181
236
  throw new Error(`Unsupported bridge method: ${method}`);
182
237
  }
@@ -0,0 +1,378 @@
1
+ export class OpenAiAgentClient {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ async decide(agent, messages) {
7
+ if (this.config.apiFormat === "chat_completions") {
8
+ return this.decideWithChatCompletions(agent, messages);
9
+ }
10
+ return this.decideWithResponses(agent, messages);
11
+ }
12
+ async decideWithResponses(agent, messages) {
13
+ const payload = await this.createResponsesRequest({
14
+ model: this.config.model || agent.model,
15
+ temperature: this.config.temperature ?? 0.2,
16
+ max_output_tokens: this.config.maxOutputTokens ?? 4000,
17
+ input: messages.map((message) => ({
18
+ role: message.role,
19
+ content: message.content,
20
+ })),
21
+ tool_choice: "required",
22
+ tools: buildResponsesDecisionTools(),
23
+ });
24
+ const decision = parseDecisionFromResponsesOutput(payload);
25
+ if (decision) {
26
+ return decision;
27
+ }
28
+ throw new Error("OpenAI responses output did not include a tool call");
29
+ }
30
+ async decideWithChatCompletions(agent, messages) {
31
+ const toolPayload = await this.createChatCompletion({
32
+ model: this.config.model || agent.model,
33
+ temperature: this.config.temperature ?? 0.2,
34
+ max_tokens: this.config.maxOutputTokens ?? 4000,
35
+ messages,
36
+ tool_choice: "required",
37
+ tools: buildChatDecisionTools(),
38
+ });
39
+ const toolDecision = parseDecisionFromToolResponse(toolPayload);
40
+ if (toolDecision) {
41
+ return toolDecision;
42
+ }
43
+ const legacyPayload = await this.createChatCompletion({
44
+ model: this.config.model || agent.model,
45
+ temperature: this.config.temperature ?? 0.2,
46
+ max_tokens: this.config.maxOutputTokens ?? 4000,
47
+ messages,
48
+ function_call: { name: "agent_decide" },
49
+ functions: [buildLegacyDecisionFunction()],
50
+ });
51
+ const legacyDecision = parseDecisionFromLegacyFunctionResponse(legacyPayload);
52
+ if (legacyDecision) {
53
+ return legacyDecision;
54
+ }
55
+ throw new Error("OpenAI chat completions output did not include a tool/function call");
56
+ }
57
+ async createResponsesRequest(body) {
58
+ const response = await fetch(`${this.config.baseUrl.replace(/\/$/, "")}/responses`, {
59
+ method: "POST",
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ Authorization: `Bearer ${this.config.apiKey}`,
63
+ },
64
+ body: JSON.stringify(body),
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error(`OpenAI responses request failed: ${response.status} ${response.statusText}`);
68
+ }
69
+ return (await response.json());
70
+ }
71
+ async createChatCompletion(body) {
72
+ const response = await fetch(`${this.config.baseUrl.replace(/\/$/, "")}/chat/completions`, {
73
+ method: "POST",
74
+ headers: {
75
+ "Content-Type": "application/json",
76
+ Authorization: `Bearer ${this.config.apiKey}`,
77
+ },
78
+ body: JSON.stringify(body),
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`OpenAI chat completions request failed: ${response.status} ${response.statusText}`);
82
+ }
83
+ return (await response.json());
84
+ }
85
+ }
86
+ function isAgentToolName(value) {
87
+ return (value === "noop" ||
88
+ value === "create_session" ||
89
+ value === "send_session_instruction" ||
90
+ value === "message_agent" ||
91
+ value === "notify_user" ||
92
+ value === "mark_project_complete");
93
+ }
94
+ function parseToolArguments(value) {
95
+ if (!value?.trim()) {
96
+ return {};
97
+ }
98
+ return JSON.parse(value);
99
+ }
100
+ function readMessageText(content) {
101
+ if (typeof content === "string") {
102
+ return content.trim();
103
+ }
104
+ if (!Array.isArray(content)) {
105
+ return "";
106
+ }
107
+ return content
108
+ .map((item) => (typeof item?.text === "string" ? item.text : ""))
109
+ .join("")
110
+ .trim();
111
+ }
112
+ function readResponsesOutputText(payload) {
113
+ const outputParts = Array.isArray(payload.output)
114
+ ? payload.output
115
+ .flatMap((item) => Array.isArray(item.content)
116
+ ? item.content.map((content) => (typeof content?.text === "string" ? content.text : ""))
117
+ : [])
118
+ .filter(Boolean)
119
+ : [];
120
+ if (outputParts.length) {
121
+ return outputParts.join("").trim();
122
+ }
123
+ return typeof payload.output_text === "string" ? payload.output_text.trim() : "";
124
+ }
125
+ function omitDecisionMeta(input) {
126
+ const { action: _action, thought: _thought, stopReason: _stopReason, userFacingText: _userFacingText, ...rest } = input;
127
+ return rest;
128
+ }
129
+ function parseDecisionFromResponsesOutput(payload) {
130
+ const toolCall = payload.output?.find((item) => item?.type === "function_call" && typeof item?.name === "string");
131
+ if (!toolCall?.name) {
132
+ return null;
133
+ }
134
+ if (!isAgentToolName(toolCall.name)) {
135
+ throw new Error(`Unsupported agent tool: ${toolCall.name}`);
136
+ }
137
+ const parsedArgs = parseToolArguments(toolCall.arguments);
138
+ return buildDecision(toolCall.name, parsedArgs, readResponsesOutputText(payload));
139
+ }
140
+ function parseDecisionFromToolResponse(payload) {
141
+ const message = payload.choices?.[0]?.message;
142
+ const toolCall = message?.tool_calls?.[0];
143
+ if (!toolCall?.function?.name) {
144
+ return null;
145
+ }
146
+ const action = toolCall.function.name;
147
+ if (!isAgentToolName(action)) {
148
+ throw new Error(`Unsupported agent tool: ${toolCall.function.name}`);
149
+ }
150
+ const parsedArgs = parseToolArguments(toolCall.function.arguments);
151
+ return buildDecision(action, parsedArgs, readMessageText(message?.content));
152
+ }
153
+ function parseDecisionFromLegacyFunctionResponse(payload) {
154
+ const message = payload.choices?.[0]?.message;
155
+ const functionCall = message?.function_call;
156
+ if (!functionCall?.name) {
157
+ return null;
158
+ }
159
+ if (functionCall.name === "agent_decide") {
160
+ const parsedArgs = parseToolArguments(functionCall.arguments);
161
+ const action = typeof parsedArgs.action === "string" ? parsedArgs.action : "";
162
+ if (!isAgentToolName(action)) {
163
+ throw new Error(`Unsupported legacy agent action: ${action || functionCall.name}`);
164
+ }
165
+ return buildDecision(action, parsedArgs, readMessageText(message?.content));
166
+ }
167
+ if (!isAgentToolName(functionCall.name)) {
168
+ throw new Error(`Unsupported legacy function call: ${functionCall.name}`);
169
+ }
170
+ const parsedArgs = parseToolArguments(functionCall.arguments);
171
+ return buildDecision(functionCall.name, parsedArgs, readMessageText(message?.content));
172
+ }
173
+ function buildDecision(action, parsedArgs, contentText) {
174
+ const thought = typeof parsedArgs.thought === "string" && parsedArgs.thought.trim()
175
+ ? parsedArgs.thought.trim()
176
+ : contentText;
177
+ const actionInput = omitDecisionMeta(parsedArgs);
178
+ return {
179
+ thought,
180
+ action,
181
+ actionInput: Object.keys(actionInput).length ? actionInput : undefined,
182
+ stopReason: typeof parsedArgs.stopReason === "string" && parsedArgs.stopReason.trim()
183
+ ? parsedArgs.stopReason.trim()
184
+ : undefined,
185
+ userFacingText: typeof parsedArgs.userFacingText === "string" && parsedArgs.userFacingText.trim()
186
+ ? parsedArgs.userFacingText.trim()
187
+ : undefined,
188
+ };
189
+ }
190
+ function buildResponsesDecisionTools() {
191
+ return buildDecisionToolSpecs().map((tool) => ({
192
+ type: "function",
193
+ name: tool.name,
194
+ description: tool.description,
195
+ parameters: tool.parameters,
196
+ }));
197
+ }
198
+ function buildChatDecisionTools() {
199
+ return buildDecisionToolSpecs().map((tool) => ({
200
+ type: "function",
201
+ function: tool,
202
+ }));
203
+ }
204
+ function buildDecisionToolSpecs() {
205
+ return [
206
+ {
207
+ name: "noop",
208
+ description: "Use when no external action should be taken right now.",
209
+ parameters: {
210
+ type: "object",
211
+ properties: commonDecisionProperties(),
212
+ additionalProperties: false,
213
+ },
214
+ },
215
+ {
216
+ name: "create_session",
217
+ description: "Create a new session for the project, optionally with an initial instruction.",
218
+ parameters: {
219
+ type: "object",
220
+ properties: {
221
+ ...commonDecisionProperties(),
222
+ cwd: { type: "string", description: "Working directory for the new session." },
223
+ model: { type: "string", description: "Optional model override." },
224
+ reasoningEffort: {
225
+ type: "string",
226
+ enum: ["none", "minimal", "low", "medium", "high", "xhigh"],
227
+ },
228
+ sandbox: {
229
+ type: "string",
230
+ enum: ["read-only", "workspace-write", "danger-full-access"],
231
+ },
232
+ approvalPolicy: {
233
+ type: "string",
234
+ enum: ["untrusted", "on-failure", "on-request", "never"],
235
+ },
236
+ instruction: {
237
+ type: "string",
238
+ description: "Optional first instruction to send to the new session immediately after creation.",
239
+ },
240
+ },
241
+ additionalProperties: false,
242
+ },
243
+ },
244
+ {
245
+ name: "send_session_instruction",
246
+ description: "Send an instruction to an existing session.",
247
+ parameters: {
248
+ type: "object",
249
+ properties: {
250
+ ...commonDecisionProperties(),
251
+ sessionId: {
252
+ type: "string",
253
+ description: "Target session id. Optional if this agent already has a bound session.",
254
+ },
255
+ instruction: {
256
+ type: "string",
257
+ description: "Instruction text to send to the target session.",
258
+ },
259
+ },
260
+ required: ["instruction"],
261
+ additionalProperties: false,
262
+ },
263
+ },
264
+ {
265
+ name: "message_agent",
266
+ description: "Send a message to another agent in the same project.",
267
+ parameters: {
268
+ type: "object",
269
+ properties: {
270
+ ...commonDecisionProperties(),
271
+ targetAgentId: { type: "string", description: "Target agent id." },
272
+ text: { type: "string", description: "Message content." },
273
+ },
274
+ required: ["targetAgentId", "text"],
275
+ additionalProperties: false,
276
+ },
277
+ },
278
+ {
279
+ name: "notify_user",
280
+ description: "Create a notification for the user, optionally via email.",
281
+ parameters: {
282
+ type: "object",
283
+ properties: {
284
+ ...commonDecisionProperties(),
285
+ severity: {
286
+ type: "string",
287
+ enum: ["info", "warning", "critical"],
288
+ },
289
+ channel: {
290
+ type: "string",
291
+ enum: ["inbox", "email"],
292
+ },
293
+ subject: { type: "string" },
294
+ body: { type: "string" },
295
+ },
296
+ additionalProperties: false,
297
+ },
298
+ },
299
+ {
300
+ name: "mark_project_complete",
301
+ description: "Mark the project as completed.",
302
+ parameters: {
303
+ type: "object",
304
+ properties: commonDecisionProperties(),
305
+ additionalProperties: false,
306
+ },
307
+ },
308
+ ];
309
+ }
310
+ function commonDecisionProperties() {
311
+ return {
312
+ thought: {
313
+ type: "string",
314
+ description: "Brief internal reasoning summary for the agent log.",
315
+ },
316
+ stopReason: {
317
+ type: "string",
318
+ description: "Why the agent is stopping or waiting after this action.",
319
+ },
320
+ userFacingText: {
321
+ type: "string",
322
+ description: "Optional concise text that can be surfaced to the user.",
323
+ },
324
+ };
325
+ }
326
+ function buildLegacyDecisionFunction() {
327
+ return {
328
+ name: "agent_decide",
329
+ description: "Choose the next agent action and provide its arguments.",
330
+ parameters: {
331
+ type: "object",
332
+ properties: {
333
+ ...commonDecisionProperties(),
334
+ action: {
335
+ type: "string",
336
+ enum: [
337
+ "noop",
338
+ "create_session",
339
+ "send_session_instruction",
340
+ "message_agent",
341
+ "notify_user",
342
+ "mark_project_complete",
343
+ ],
344
+ },
345
+ cwd: { type: "string" },
346
+ model: { type: "string" },
347
+ reasoningEffort: {
348
+ type: "string",
349
+ enum: ["none", "minimal", "low", "medium", "high", "xhigh"],
350
+ },
351
+ sandbox: {
352
+ type: "string",
353
+ enum: ["read-only", "workspace-write", "danger-full-access"],
354
+ },
355
+ approvalPolicy: {
356
+ type: "string",
357
+ enum: ["untrusted", "on-failure", "on-request", "never"],
358
+ },
359
+ instruction: { type: "string" },
360
+ sessionId: { type: "string" },
361
+ text: { type: "string" },
362
+ targetAgentId: { type: "string" },
363
+ severity: {
364
+ type: "string",
365
+ enum: ["info", "warning", "critical"],
366
+ },
367
+ channel: {
368
+ type: "string",
369
+ enum: ["inbox", "email"],
370
+ },
371
+ subject: { type: "string" },
372
+ body: { type: "string" },
373
+ },
374
+ required: ["action"],
375
+ additionalProperties: false,
376
+ },
377
+ };
378
+ }