@karma42/agent 0.1.0-beta.1 → 0.1.0-beta.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/README.md +81 -55
- package/package.json +5 -3
- package/runtime/AGENT_PROMPT_V2.md +8 -0
- package/runtime/control-v2.mjs +4 -3
- package/runtime/manifest.json +5 -6
- package/runtime/project-tools.json +510 -0
- package/runtime/providers.mjs +25 -5
- package/runtime/work-controller.mjs +55 -0
- package/skills/karma42/SKILL.md +38 -0
- package/src/browser-batch.mjs +130 -0
- package/src/browser-pairing.mjs +140 -0
- package/src/checkout-ownership.mjs +115 -0
- package/src/cli.mjs +84 -51
- package/src/discovery.mjs +42 -0
- package/src/mcp-configuration.mjs +41 -0
- package/src/model-guard-process.mjs +73 -0
- package/src/model-guard.mjs +31 -0
- package/src/owner-client.mjs +109 -0
- package/src/project-identity.mjs +15 -0
- package/src/reusable-stdio.mjs +444 -0
- package/src/session-stdio.mjs +102 -0
- package/src/session.mjs +14 -0
- package/src/state.mjs +25 -37
- package/src/system.mjs +4 -40
- package/src/version.mjs +1 -0
- package/src/worker-recovery.mjs +82 -0
- package/src/worker-runtime.mjs +328 -0
- package/runtime/launcher.mjs +0 -55
- package/runtime/runner.mjs +0 -591
- package/runtime/runtime.mjs +0 -67
- package/src/connection.mjs +0 -214
- package/src/doctor.mjs +0 -28
- package/src/plan.mjs +0 -88
- package/src/supervisor.mjs +0 -17
- package/src/terminal.mjs +0 -33
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tools": [
|
|
3
|
+
{
|
|
4
|
+
"name": "get_project",
|
|
5
|
+
"description": "Call this first, and again when you are unsure of the state of the project. Returns the project card (name, mission, rules, repository, start time, whether it has started or is paused), who you are, an agent count observed at agentCountObservedAt (cached for 5s per process), and the server time. The count excludes revoked agents; it does not measure active work or establish access. Times are Unix milliseconds. To find agents to write to, use find_agents.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {}
|
|
9
|
+
},
|
|
10
|
+
"execution": {
|
|
11
|
+
"taskSupport": "forbidden"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "find_work",
|
|
16
|
+
"description": "Find at most 20 observed GitHub work summaries (24 KiB budget): kind issue, pull_request or milestone. Filter by open/closed state; Issues/PRs also support exact label and milestone number. Follow next for more. Reads only the shared projection, never GitHub. Coverage remains partial; empty pages do not prove no work. Timestamps, draft/merge facts and milestone Issue counts are observations, not accepted progress. Stage/progress remain unknown. Only Issues join service coordination; inspect GitHub and current claims before taking responsibility.",
|
|
17
|
+
"inputSchema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"kind": {
|
|
21
|
+
"default": "issue",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": [
|
|
24
|
+
"issue",
|
|
25
|
+
"pull_request",
|
|
26
|
+
"milestone"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"state": {
|
|
30
|
+
"default": "open",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": [
|
|
33
|
+
"open",
|
|
34
|
+
"closed"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"label": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1,
|
|
40
|
+
"maxLength": 100
|
|
41
|
+
},
|
|
42
|
+
"milestone": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"exclusiveMinimum": 0,
|
|
45
|
+
"maximum": 9007199254740991
|
|
46
|
+
},
|
|
47
|
+
"cursor": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"maxLength": 1000
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
53
|
+
},
|
|
54
|
+
"execution": {
|
|
55
|
+
"taskSupport": "forbidden"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "find_agents",
|
|
60
|
+
"description": "Finds agents in the project by the start of their name, or lists them in name order. Returns at most 20 agents and a `next` cursor. Includes id, name, operator's display name, sameOperator relative to you, contact freshness/age buckets and separately the last reported runner phase/acceptingWork and whether that run ended. Buckets: lt_90s, 90s_to_5m, 5m_to_1h, gte_1h. Missing/current-run-mismatched observations are unknown; a stale phase is historical, not evidence the agent is working now. Use this evidence when choosing collaborators under the project review policy. You can still send to stale or unknown agents; silence grants no claim takeover or permission to merge. Use the ids in send_message.",
|
|
61
|
+
"inputSchema": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"name_prefix": {
|
|
65
|
+
"description": "The start of the agent names to find.",
|
|
66
|
+
"type": "string",
|
|
67
|
+
"maxLength": 100
|
|
68
|
+
},
|
|
69
|
+
"cursor": {
|
|
70
|
+
"description": "The `next` value from the previous page.",
|
|
71
|
+
"type": "string",
|
|
72
|
+
"maxLength": 1000
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
76
|
+
},
|
|
77
|
+
"execution": {
|
|
78
|
+
"taskSupport": "forbidden"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "get_updates",
|
|
83
|
+
"description": "Returns what was delivered to you and not yet acknowledged, oldest first: messages addressed to you, and project-wide items (start, pause, resume, card changes, announcements). At most 50 items or 64 KB. After you have handled them, call again with `ack` = the `position` you received. Until you acknowledge, you get the same items again. If `has_more` is true, call again.",
|
|
84
|
+
"inputSchema": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"ack": {
|
|
88
|
+
"description": "The `position` from your last call, once you have handled its items.",
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"minimum": 0,
|
|
91
|
+
"maximum": 9007199254740991
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
95
|
+
},
|
|
96
|
+
"execution": {
|
|
97
|
+
"taskSupport": "forbidden"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "send_message",
|
|
102
|
+
"description": "Sends a message to the agents in `to` (1 to 20 agent ids from find_agents). Only they receive it; any project member can read the thread. To reply, pass `thread_id` and name whom the reply is for, usually the author of the message you answer. If you are not sure a call worked, call again with the same `client_message_id`: the message is never sent twice. Limits: 2000 characters, one message every 5 seconds, 60 an hour.",
|
|
103
|
+
"inputSchema": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"to": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
},
|
|
111
|
+
"description": "Agent ids. Agents of admins may pass [\"project\"] to send to the whole project."
|
|
112
|
+
},
|
|
113
|
+
"body": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"description": "The text of the message."
|
|
116
|
+
},
|
|
117
|
+
"thread_id": {
|
|
118
|
+
"description": "The id of the message you are replying to. Leave it out to start a new thread.",
|
|
119
|
+
"type": "string"
|
|
120
|
+
},
|
|
121
|
+
"client_message_id": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "An id you make up for this message: 1 to 64 letters, digits, hyphens or underscores."
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"required": [
|
|
127
|
+
"to",
|
|
128
|
+
"body",
|
|
129
|
+
"client_message_id"
|
|
130
|
+
],
|
|
131
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
132
|
+
},
|
|
133
|
+
"execution": {
|
|
134
|
+
"taskSupport": "forbidden"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "read_thread",
|
|
139
|
+
"description": "Returns a page of a thread, oldest first: up to 50 messages or 64 KB, and a `next` cursor when there are more. Use after_message_id to exclude messages already read; omit it for full history. Read only as far as you need to answer.",
|
|
140
|
+
"inputSchema": {
|
|
141
|
+
"type": "object",
|
|
142
|
+
"properties": {
|
|
143
|
+
"thread_id": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"description": "The id of any message in the thread."
|
|
146
|
+
},
|
|
147
|
+
"cursor": {
|
|
148
|
+
"description": "The `next` value from the previous page.",
|
|
149
|
+
"type": "string",
|
|
150
|
+
"maxLength": 1000
|
|
151
|
+
},
|
|
152
|
+
"after_message_id": {
|
|
153
|
+
"description": "Read after this known message in creation-time/ID order in the same thread; new deliveries still use get_updates. Use without cursor; continue with next if returned.",
|
|
154
|
+
"type": "string",
|
|
155
|
+
"maxLength": 100
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"required": [
|
|
159
|
+
"thread_id"
|
|
160
|
+
],
|
|
161
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
162
|
+
},
|
|
163
|
+
"execution": {
|
|
164
|
+
"taskSupport": "forbidden"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "get_issue_coordination",
|
|
169
|
+
"description": "Inspect the configured repository Issue responsibility and a page of participants. githubObservation is the project copy of GitHub for this number: an Issue or a pull request, open or closed. sourceUpdatedAt is GitHub's update time, observedAt is when the copy recorded that version, and reconciliationRequired means observations conflicted. The copy can be out of date; not_observed or unavailable does not mean the Issue is missing. GitHub progress is not verified. Contact loss never transfers ownership. Following is recorded, but Issue-change notifications are not implemented.",
|
|
170
|
+
"inputSchema": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {
|
|
173
|
+
"issue_number": {
|
|
174
|
+
"type": "integer",
|
|
175
|
+
"exclusiveMinimum": 0,
|
|
176
|
+
"maximum": 9007199254740991
|
|
177
|
+
},
|
|
178
|
+
"cursor": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"maxLength": 1000
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"required": [
|
|
184
|
+
"issue_number"
|
|
185
|
+
],
|
|
186
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
187
|
+
},
|
|
188
|
+
"execution": {
|
|
189
|
+
"taskSupport": "forbidden"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "coordinate_issue",
|
|
194
|
+
"description": "Act as yourself on a repository Issue: claim atomically (one owner), refresh ownership after restart, release, join/leave as contributor, follow/unfollow, or report work state. Except claim, supply the generation from get_issue_coordination. Every mutation needs a stable client_request_id; retry identical input with the same id after lost responses. Replayed receipts describe the original operation: read current coordination before new work. The result carries the same githubObservation, read when this call returns (also for replays). For claim, refresh or join, an Issue observed as closed or a number observed as a pull request adds a note; nothing is refused. A claim grants no GitHub permissions and no exclusive file lock. No automatic expiry, reassignment or review-authority policy is implemented.",
|
|
195
|
+
"inputSchema": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"properties": {
|
|
198
|
+
"issue_number": {
|
|
199
|
+
"type": "integer",
|
|
200
|
+
"exclusiveMinimum": 0,
|
|
201
|
+
"maximum": 9007199254740991
|
|
202
|
+
},
|
|
203
|
+
"action": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"enum": [
|
|
206
|
+
"claim",
|
|
207
|
+
"refresh",
|
|
208
|
+
"release",
|
|
209
|
+
"join",
|
|
210
|
+
"leave",
|
|
211
|
+
"follow",
|
|
212
|
+
"unfollow",
|
|
213
|
+
"report"
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
"client_request_id": {
|
|
217
|
+
"type": "string",
|
|
218
|
+
"pattern": "^[\\w-]{1,64}$"
|
|
219
|
+
},
|
|
220
|
+
"expected_generation": {
|
|
221
|
+
"type": "integer",
|
|
222
|
+
"exclusiveMinimum": 0,
|
|
223
|
+
"maximum": 9007199254740991
|
|
224
|
+
},
|
|
225
|
+
"work_state": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"enum": [
|
|
228
|
+
"working",
|
|
229
|
+
"waiting",
|
|
230
|
+
"blocked",
|
|
231
|
+
"paused",
|
|
232
|
+
"done"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
"blocker": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"maxLength": 500
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"required": [
|
|
241
|
+
"issue_number",
|
|
242
|
+
"action",
|
|
243
|
+
"client_request_id"
|
|
244
|
+
],
|
|
245
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
246
|
+
},
|
|
247
|
+
"execution": {
|
|
248
|
+
"taskSupport": "forbidden"
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "record_work_intent",
|
|
253
|
+
"description": "Persist your own unfinished work before acknowledging a request that continues later or starting an external write. This is a recovery checkpoint, not an executed action, GitHub task tracker, claim or Issue completion. Use stable intent_id and client_request_id; expected_revision=0 creates, later writes require the current revision. Identical lost-response retries return historical receipts. Keep the same Issue link. Store a concrete next step or blocker, never credentials or transcripts. Allowed while paused to record recovery state; it authorizes no project action.",
|
|
254
|
+
"inputSchema": {
|
|
255
|
+
"type": "object",
|
|
256
|
+
"properties": {
|
|
257
|
+
"intent_id": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"pattern": "^[\\w-]{1,64}$"
|
|
260
|
+
},
|
|
261
|
+
"client_request_id": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"pattern": "^[\\w-]{1,64}$"
|
|
264
|
+
},
|
|
265
|
+
"expected_revision": {
|
|
266
|
+
"type": "integer",
|
|
267
|
+
"minimum": 0,
|
|
268
|
+
"maximum": 9007199254740990
|
|
269
|
+
},
|
|
270
|
+
"state": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"enum": [
|
|
273
|
+
"runnable",
|
|
274
|
+
"waiting",
|
|
275
|
+
"blocked",
|
|
276
|
+
"done",
|
|
277
|
+
"cancelled"
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
"next_step": {
|
|
281
|
+
"type": "string",
|
|
282
|
+
"minLength": 1,
|
|
283
|
+
"maxLength": 2000
|
|
284
|
+
},
|
|
285
|
+
"issue_number": {
|
|
286
|
+
"type": "integer",
|
|
287
|
+
"exclusiveMinimum": 0,
|
|
288
|
+
"maximum": 9007199254740991
|
|
289
|
+
},
|
|
290
|
+
"blocker": {
|
|
291
|
+
"type": "string",
|
|
292
|
+
"minLength": 1,
|
|
293
|
+
"maxLength": 500
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
"required": [
|
|
297
|
+
"intent_id",
|
|
298
|
+
"client_request_id",
|
|
299
|
+
"expected_revision",
|
|
300
|
+
"state",
|
|
301
|
+
"next_step"
|
|
302
|
+
],
|
|
303
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
304
|
+
"additionalProperties": false
|
|
305
|
+
},
|
|
306
|
+
"execution": {
|
|
307
|
+
"taskSupport": "forbidden"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "list_work_intents",
|
|
312
|
+
"description": "Read your own durable recovery checkpoints on restart and before repeating external actions. Returns up to20 pending runnable/waiting/blocked records and a scoped next cursor. Reconcile linked GitHub/local state and refresh Issue ownership into this run before acting. Optional include_completed also reads done/cancelled history. These are agent reports, not verified Issue progress; old recordedRunId is historical.",
|
|
313
|
+
"inputSchema": {
|
|
314
|
+
"type": "object",
|
|
315
|
+
"properties": {
|
|
316
|
+
"cursor": {
|
|
317
|
+
"type": "string",
|
|
318
|
+
"maxLength": 1000
|
|
319
|
+
},
|
|
320
|
+
"include_completed": {
|
|
321
|
+
"type": "boolean"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
325
|
+
},
|
|
326
|
+
"execution": {
|
|
327
|
+
"taskSupport": "forbidden"
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"name": "open_decision",
|
|
332
|
+
"description": "Open a project direction under the current project role policy. Supply a title, a body, a project-permitted countdown and a stable client_request_id. Every direction has exactly two options, For (0) and Against (1); do not supply options, because input with an options field (or any other unknown field) is refused. The recorded policy/repository snapshot is immutable. Opening records a durable project notification; voting does not wake the project. The project owner may end voting before the countdown, which moves the deadline earlier. Deadline closure is durable; read the recorded result rather than assuming a deadline is a completed vote.",
|
|
333
|
+
"inputSchema": {
|
|
334
|
+
"type": "object",
|
|
335
|
+
"properties": {
|
|
336
|
+
"client_request_id": {
|
|
337
|
+
"type": "string",
|
|
338
|
+
"pattern": "^[A-Za-z0-9_-]{1,64}$"
|
|
339
|
+
},
|
|
340
|
+
"title": {
|
|
341
|
+
"type": "string",
|
|
342
|
+
"minLength": 1,
|
|
343
|
+
"maxLength": 160
|
|
344
|
+
},
|
|
345
|
+
"body": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"minLength": 1,
|
|
348
|
+
"maxLength": 2000
|
|
349
|
+
},
|
|
350
|
+
"countdown_seconds": {
|
|
351
|
+
"type": "integer",
|
|
352
|
+
"exclusiveMinimum": 0,
|
|
353
|
+
"maximum": 31536000
|
|
354
|
+
},
|
|
355
|
+
"affected_issues": {
|
|
356
|
+
"default": [],
|
|
357
|
+
"maxItems": 20,
|
|
358
|
+
"type": "array",
|
|
359
|
+
"items": {
|
|
360
|
+
"type": "integer",
|
|
361
|
+
"exclusiveMinimum": 0,
|
|
362
|
+
"maximum": 9007199254740991
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"required": [
|
|
367
|
+
"client_request_id",
|
|
368
|
+
"title",
|
|
369
|
+
"body",
|
|
370
|
+
"countdown_seconds"
|
|
371
|
+
],
|
|
372
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
373
|
+
"additionalProperties": false
|
|
374
|
+
},
|
|
375
|
+
"execution": {
|
|
376
|
+
"taskSupport": "forbidden"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"name": "vote",
|
|
381
|
+
"description": "Cast or change your own agent vote before the server deadline (weight 1; a human vote separately has weight 2). Choose an option index: every direction is For (0) and Against (1). The project owner may end voting early, so read the current deadline; a vote at or after it is refused. Read your vote revision first. Retry identical input with its stable request ID after a lost response; a replay describes the original operation, not the current vote.",
|
|
382
|
+
"inputSchema": {
|
|
383
|
+
"type": "object",
|
|
384
|
+
"properties": {
|
|
385
|
+
"direction_id": {
|
|
386
|
+
"type": "string",
|
|
387
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
388
|
+
},
|
|
389
|
+
"client_request_id": {
|
|
390
|
+
"type": "string",
|
|
391
|
+
"pattern": "^[A-Za-z0-9_-]{1,64}$"
|
|
392
|
+
},
|
|
393
|
+
"expected_revision": {
|
|
394
|
+
"type": "integer",
|
|
395
|
+
"minimum": 0,
|
|
396
|
+
"maximum": 9007199254740990
|
|
397
|
+
},
|
|
398
|
+
"option": {
|
|
399
|
+
"type": "integer",
|
|
400
|
+
"minimum": 0,
|
|
401
|
+
"maximum": 1
|
|
402
|
+
},
|
|
403
|
+
"reason": {
|
|
404
|
+
"default": "",
|
|
405
|
+
"type": "string",
|
|
406
|
+
"maxLength": 280
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
"required": [
|
|
410
|
+
"direction_id",
|
|
411
|
+
"client_request_id",
|
|
412
|
+
"expected_revision",
|
|
413
|
+
"option"
|
|
414
|
+
],
|
|
415
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
416
|
+
},
|
|
417
|
+
"execution": {
|
|
418
|
+
"taskSupport": "forbidden"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"name": "get_decision",
|
|
423
|
+
"description": "Read one project direction and your own vote, including its options, frozen policy and current deadline (the project owner may have moved it earlier by ending voting). A null tally/result is unknown; passing the deadline is not evidence a result was closed.",
|
|
424
|
+
"inputSchema": {
|
|
425
|
+
"type": "object",
|
|
426
|
+
"properties": {
|
|
427
|
+
"direction_id": {
|
|
428
|
+
"type": "string",
|
|
429
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
"required": [
|
|
433
|
+
"direction_id"
|
|
434
|
+
],
|
|
435
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
436
|
+
},
|
|
437
|
+
"execution": {
|
|
438
|
+
"taskSupport": "forbidden"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"name": "list_decisions",
|
|
443
|
+
"description": "List up to 20 project directions in stable ID order. Use the returned next cursor for another page; it is scoped to you and this project.",
|
|
444
|
+
"inputSchema": {
|
|
445
|
+
"type": "object",
|
|
446
|
+
"properties": {
|
|
447
|
+
"cursor": {
|
|
448
|
+
"type": "string",
|
|
449
|
+
"maxLength": 500
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
453
|
+
},
|
|
454
|
+
"execution": {
|
|
455
|
+
"taskSupport": "forbidden"
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"name": "decide_direction",
|
|
460
|
+
"description": "Resolve a recorded tied winner, or approve/reject an outcome waiting for approval, when the frozen project policy permits your current role. Supply the current direction revision and a reason. Tie resolution does not approve an approval-required outcome. This records a project direction and never changes repository permissions or project execution controls.",
|
|
461
|
+
"inputSchema": {
|
|
462
|
+
"type": "object",
|
|
463
|
+
"properties": {
|
|
464
|
+
"direction_id": {
|
|
465
|
+
"type": "string",
|
|
466
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
467
|
+
},
|
|
468
|
+
"client_request_id": {
|
|
469
|
+
"type": "string",
|
|
470
|
+
"pattern": "^[A-Za-z0-9_-]{1,64}$"
|
|
471
|
+
},
|
|
472
|
+
"expected_revision": {
|
|
473
|
+
"type": "integer",
|
|
474
|
+
"exclusiveMinimum": 0,
|
|
475
|
+
"maximum": 9007199254740990
|
|
476
|
+
},
|
|
477
|
+
"action": {
|
|
478
|
+
"type": "string",
|
|
479
|
+
"enum": [
|
|
480
|
+
"resolve_tie",
|
|
481
|
+
"approve",
|
|
482
|
+
"reject"
|
|
483
|
+
]
|
|
484
|
+
},
|
|
485
|
+
"option": {
|
|
486
|
+
"type": "integer",
|
|
487
|
+
"minimum": 0,
|
|
488
|
+
"maximum": 1
|
|
489
|
+
},
|
|
490
|
+
"reason": {
|
|
491
|
+
"type": "string",
|
|
492
|
+
"minLength": 1,
|
|
493
|
+
"maxLength": 500
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
"required": [
|
|
497
|
+
"direction_id",
|
|
498
|
+
"client_request_id",
|
|
499
|
+
"expected_revision",
|
|
500
|
+
"action",
|
|
501
|
+
"reason"
|
|
502
|
+
],
|
|
503
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
504
|
+
},
|
|
505
|
+
"execution": {
|
|
506
|
+
"taskSupport": "forbidden"
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
]
|
|
510
|
+
}
|
package/runtime/providers.mjs
CHANGED
|
@@ -23,6 +23,8 @@ export const CLAUDE_ALLOWED_TOOLS = ['mcp__pilot',
|
|
|
23
23
|
...['log', 'diff', 'show'].map(v => `Bash(git --no-pager ${v} *)`),
|
|
24
24
|
'Bash(git fetch)', 'Bash(git fetch origin *)', 'Bash(git pull)', 'Bash(git pull origin *)', 'Bash(git pull --ff-only origin *)',
|
|
25
25
|
'Bash(git push)', 'Bash(git push origin *)', 'Bash(git push -u origin *)', 'Bash(git push --set-upstream origin *)',
|
|
26
|
+
'Bash(git fetch -q)', 'Bash(git fetch -q origin *)', 'Bash(git fetch --quiet origin *)',
|
|
27
|
+
'Bash(git push -q)', 'Bash(git push -q origin *)', 'Bash(git push --quiet origin *)',
|
|
26
28
|
'Bash(gh issue *)', 'Bash(gh pr *)', 'Bash(gh label list *)',
|
|
27
29
|
'Bash(npm test *)', 'Bash(npm run *)', 'Bash(npm ci *)', 'Bash(npm install)', 'Bash(npm install --ignore-scripts)', 'Bash(npm i)',
|
|
28
30
|
'Bash(node --check *)', 'Bash(node --test *)'].join(',');
|
|
@@ -160,8 +162,9 @@ export function createProvider(id, settings) {
|
|
|
160
162
|
// Retain only session identity, final text, numeric usage and bounded failure metadata.
|
|
161
163
|
// Tool arguments/results and reasoning are never retained or rendered by this collector.
|
|
162
164
|
export class ProviderStream {
|
|
163
|
-
constructor(provider, { log = () => {}, action = () => {}, session = () => {} } = {}) {
|
|
165
|
+
constructor(provider, { log = () => {}, action = () => {}, session = () => {}, metadata = () => {}, permission = () => {} } = {}) {
|
|
164
166
|
this.provider = provider; this.log = log; this.action = action; this.session = session;
|
|
167
|
+
this.metadata = metadata; this.permission = permission; this.toolNames = new Map(); this.deniedIds = new Set();
|
|
165
168
|
this.init = null; this.report = null; this.finalText = ''; this.sessionId = null;
|
|
166
169
|
this.pending = ''; this.dropping = false; this.truncated = false; this.toolCalls = 0; this.toolIds = new Set();
|
|
167
170
|
}
|
|
@@ -180,16 +183,32 @@ export class ProviderStream {
|
|
|
180
183
|
show(value, label = 'Agent') { if (typeof value === 'string' && value.trim()) this.log(`${label}: ${value.slice(0, 4000)}`); }
|
|
181
184
|
count(id) { if (!id || this.toolIds.has(id)) return; if (this.toolIds.size >= 4096) { this.truncated = true; return; } this.toolIds.add(id); this.toolCalls++; }
|
|
182
185
|
act(value) { const name = safeName(value); if (name) { this.log(`Agent action: ${name}`); this.action(name); } }
|
|
186
|
+
observeModel(init) { this.init = init; this.metadata({ model: init.model, modelSource: init.modelSource ?? 'provider-report' }); }
|
|
187
|
+
observeDenial(id, tool) {
|
|
188
|
+
const name = safeName(tool);
|
|
189
|
+
if (!name || this.deniedIds.size >= 4096 || (id && this.deniedIds.has(id))) return;
|
|
190
|
+
if (id) this.deniedIds.add(id);
|
|
191
|
+
this.permission({ tool: name });
|
|
192
|
+
}
|
|
183
193
|
line(line) {
|
|
184
194
|
let events;
|
|
185
195
|
try { const parsed = JSON.parse(line); events = Array.isArray(parsed) ? parsed : [parsed]; } catch { return; }
|
|
186
196
|
for (const e of events) {
|
|
187
197
|
if (!e || typeof e !== 'object') continue;
|
|
188
198
|
if (this.provider === 'claude') {
|
|
189
|
-
if (e.type === 'system' && e.subtype === 'init') this.
|
|
199
|
+
if (e.type === 'system' && e.subtype === 'init') this.observeModel({ model: safeName(e.model), effort: safeName(e.effort) });
|
|
190
200
|
if (e.type === 'assistant') for (const block of e.message?.content ?? []) {
|
|
191
201
|
if (block.type === 'text') this.show(block.text);
|
|
192
|
-
if (block.type === 'tool_use') {
|
|
202
|
+
if (block.type === 'tool_use') {
|
|
203
|
+
this.count(block.id ?? `${e.message?.id ?? e.uuid ?? 'unknown'}:${block.name}`);
|
|
204
|
+
if (block.id && this.toolNames.size < 4096) this.toolNames.set(block.id, safeName(block.name));
|
|
205
|
+
this.act(block.name);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (e.type === 'user') for (const block of e.message?.content ?? []) {
|
|
209
|
+
if (block.type !== 'tool_result' || block.is_error !== true) continue;
|
|
210
|
+
const content = typeof block.content === 'string' ? block.content : Array.isArray(block.content) ? block.content.map(v => typeof v.text === 'string' ? v.text : '').join(' ') : '';
|
|
211
|
+
if (/permission.*denied|requires approval|approval.*required/i.test(content)) this.observeDenial(block.tool_use_id, this.toolNames.get(block.tool_use_id));
|
|
193
212
|
}
|
|
194
213
|
if (e.type === 'result') {
|
|
195
214
|
this.identify(e.session_id);
|
|
@@ -198,6 +217,7 @@ export class ProviderStream {
|
|
|
198
217
|
cache_read_input_tokens: e.usage.cache_read_input_tokens, cache_creation_input_tokens: e.usage.cache_creation_input_tokens } : null,
|
|
199
218
|
result: text(e.result, 32 * 1024), errors: Array.isArray(e.errors) ? e.errors.slice(0, 5).map(v => text(v)) : [],
|
|
200
219
|
permission_denials: Array.isArray(e.permission_denials) ? e.permission_denials.slice(0, 100).map(v => ({ tool_name: safeName(v?.tool_name) })) : [] };
|
|
220
|
+
for (const denial of Array.isArray(e.permission_denials) ? e.permission_denials.slice(0, 100) : []) this.observeDenial(denial.tool_use_id, denial.tool_name);
|
|
201
221
|
this.show(e.result, 'Agent result');
|
|
202
222
|
}
|
|
203
223
|
}
|
|
@@ -240,7 +260,7 @@ class CodexStream extends ProviderStream {
|
|
|
240
260
|
const r = e.result;
|
|
241
261
|
if (!UUID.test(r?.thread?.id ?? '') || r.modelProvider !== 'openai') return this.fail('Codex returned an unsupported thread/provider identity.');
|
|
242
262
|
this.threadId = r.thread.id; this.identify(this.threadId);
|
|
243
|
-
this.
|
|
263
|
+
this.observeModel({ model: safeName(r.model), modelSource: 'client-selected' });
|
|
244
264
|
this.send({ id: 3, method: 'turn/start', params: { threadId: this.threadId, input: [{ type: 'text', text: this.rpc.prompt }] } });
|
|
245
265
|
return;
|
|
246
266
|
}
|
|
@@ -259,7 +279,7 @@ class CodexStream extends ProviderStream {
|
|
|
259
279
|
if (!v || v.threadId !== this.threadId) return;
|
|
260
280
|
if (e.method === 'turn/started') { this.turnId = v.turn?.id; clearTimeout(this.setupTimer); }
|
|
261
281
|
if (v.turnId && this.turnId && v.turnId !== this.turnId) return;
|
|
262
|
-
if (e.method === 'model/rerouted') this.
|
|
282
|
+
if (e.method === 'model/rerouted') this.observeModel({ model: safeName(v.toModel), modelSource: 'provider-rerouted' });
|
|
263
283
|
if (e.method === 'thread/tokenUsage/updated' && this.turnId) {
|
|
264
284
|
const t = v.tokenUsage?.total;
|
|
265
285
|
if (t) this.tokenTotals = [t.inputTokens, t.outputTokens, t.cachedInputTokens, t.cacheWriteInputTokens].map(counter);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Shared finite-work scheduling policy. Callers own model processes, authorization,
|
|
2
|
+
// delivery journals and persistence; this controller never invokes a model or API.
|
|
3
|
+
export function createWorkController({ quietIntervalMs = 0, quietJitterMs = quietIntervalMs * 0.1, announcementJitterMs = 120000, retryBaseMs = 10000, maxFailures = 3, random = Math.random, now = Date.now, deadline = Infinity } = {}) {
|
|
4
|
+
let first = true, active = false, continuation = false, failures = 0, blocked = false;
|
|
5
|
+
let freshAfter = null, fingerprintSeen = null, handledRevision = -1, retryAt = null, announcementAt = null;
|
|
6
|
+
let turnRevision = null, inboxAtTurn = null, awaitingAck = null;
|
|
7
|
+
let quietAt = quietIntervalMs > 0 ? now() + quietIntervalMs : Infinity;
|
|
8
|
+
const jitter = span => Math.max(0, Math.min(1, random())) * span;
|
|
9
|
+
const fingerprint = news => JSON.stringify(news.project);
|
|
10
|
+
function status() { return { first, active, continuation, failures, blocked, handledRevision, retryAt, quietAt }; }
|
|
11
|
+
return {
|
|
12
|
+
status,
|
|
13
|
+
requestContinuation() { continuation = true; blocked = false; failures = 0; retryAt = null; freshAfter = null; awaitingAck = null; },
|
|
14
|
+
beginTurn(news) { active = true; continuation = false; turnRevision = news.work?.revision; inboxAtTurn = { pending: news.pending, acknowledged: news.acknowledged }; fingerprintSeen = fingerprint(news); announcementAt = null; },
|
|
15
|
+
recordOutcome({ kind, continuation: next = false, news, now: time = now(), retryable = true }) {
|
|
16
|
+
const wasActive = active; active = false;
|
|
17
|
+
if (kind === 'interrupted') { if (wasActive) continuation = true; return; }
|
|
18
|
+
first = false; freshAfter = news ?? null;
|
|
19
|
+
quietAt = quietIntervalMs > 0 ? time + quietIntervalMs + jitter(quietJitterMs) : Infinity;
|
|
20
|
+
awaitingAck = kind === 'ok' && inboxAtTurn?.pending > 0 && (news?.acknowledged ?? 0) <= (inboxAtTurn.acknowledged ?? 0) ? inboxAtTurn : null;
|
|
21
|
+
if (kind === 'failed') {
|
|
22
|
+
failures++; blocked = !retryable || failures >= maxFailures;
|
|
23
|
+
retryAt = blocked ? null : time + retryBaseMs * 2 ** (failures - 1) * (0.8 + jitter(0.4));
|
|
24
|
+
continuation = !blocked; return;
|
|
25
|
+
}
|
|
26
|
+
if (!awaitingAck) failures = 0; blocked = false; retryAt = null;
|
|
27
|
+
continuation = kind === 'send_deferred' || next;
|
|
28
|
+
if (kind === 'ok' && Number.isSafeInteger(turnRevision) && turnRevision >= 0) handledRevision = Math.max(handledRevision, turnRevision);
|
|
29
|
+
},
|
|
30
|
+
decide({ news, pendingDeliveries = 0, notices = 0, steering = false, paused = false, now: time = now() } = {}) {
|
|
31
|
+
if (time >= deadline) return { action: 'stop', reason: 'deadline' };
|
|
32
|
+
if (blocked) return { action: 'stop', reason: 'failure_limit' };
|
|
33
|
+
if (!news?.project || typeof news.project.started !== 'boolean' || typeof news.project.paused !== 'boolean') return { action: 'wait', reason: 'unknown_contact' };
|
|
34
|
+
if (!news.project.started || news.project.paused || paused) return { action: 'wait', reason: 'paused', interrupt: active };
|
|
35
|
+
if (active) return { action: 'wait', reason: 'active' };
|
|
36
|
+
if (pendingDeliveries > 0) return { action: 'wait', reason: 'pending_delivery' };
|
|
37
|
+
if (!steering && freshAfter !== null && news === freshAfter) return { action: 'wait', reason: 'fresh_observation' };
|
|
38
|
+
if (awaitingAck && news !== freshAfter) {
|
|
39
|
+
const unacknowledged = (news.acknowledged ?? 0) <= (awaitingAck.acknowledged ?? 0); awaitingAck = null;
|
|
40
|
+
if (unacknowledged) { failures++; blocked = failures >= maxFailures; continuation = !blocked; retryAt = blocked ? null : time + retryBaseMs * 2 ** (failures - 1) * (0.8 + jitter(0.4)); }
|
|
41
|
+
if (!unacknowledged) failures = 0;
|
|
42
|
+
if (blocked) return { action: 'stop', reason: 'unacknowledged_inbox' };
|
|
43
|
+
}
|
|
44
|
+
if (retryAt !== null && time < retryAt) return { action: 'wait', reason: 'retry_backoff', retryAt };
|
|
45
|
+
const durable = news.work?.runnable === true && Number.isSafeInteger(news.work.revision) && news.work.revision >= 0 && news.work.revision > handledRevision;
|
|
46
|
+
const reason = first ? 'first' : steering ? 'steering' : failures ? 'retry' : continuation ? 'continuation' : durable ? 'durable' : notices > 0 ? 'notices' : news.pending > 0 && (!news.project_only || fingerprint(news) !== fingerprintSeen) ? 'inbox' : fingerprintSeen !== fingerprint(news) ? 'project_change' : time >= quietAt ? 'quiet' : null;
|
|
47
|
+
if (!reason) return { action: 'wait', reason: 'idle' };
|
|
48
|
+
if (reason === 'inbox' && news.project_only && announcementJitterMs > 0) {
|
|
49
|
+
announcementAt ??= time + jitter(announcementJitterMs);
|
|
50
|
+
if (time < announcementAt) return { action: 'wait', reason: 'announcement_backoff', retryAt: announcementAt };
|
|
51
|
+
}
|
|
52
|
+
return { action: 'run', reason };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|