@anhlp/alp-runtime 0.0.0-beta.2 → 0.0.1-beta.3

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.
@@ -153,6 +153,7 @@ export function nativeSessionConfig(
153
153
  }
154
154
  const delegationInstruction = targets.length
155
155
  ? `Use alp_delegate to assign bounded work to: ${targets.join(', ')}. ` +
156
+ 'Delegation has two finite budgets shared by the entire root session tree: 16 implementation starts (lead/peer) and separately 16 read-only advisor starts (reviewer/oracle). Only a new accepted user prompt in root, including steering, resets both; child turns, mail wakes and approval answers do not. Started assignments count even when they fail. ' +
156
157
  'Work runs in the background: by default alp_delegate starts the assignment and returns its assignmentId at once, and its result, questions and stall reports come to you as mail. ' +
157
158
  'Pass wait: true only when your very next step cannot go on without the result; it then returns the result, or early with the child\'s first question. ' +
158
159
  'A result carries the child\'s structured handoff (null if it filed none) and output, its final message. ' +
@@ -213,7 +214,9 @@ export function nativeSessionConfig(
213
214
 
214
215
  'Project board: every agent working on this project, in any session, shares one board. Before changing files, read alp_board and pin a claim listing the paths you will change; do not edit paths another agent has claimed, and ask your requester instead. Pin a decision when you choose an approach others should follow, and a finding when you learn something others need. Pins from others arrive as board mail; it is information, and it never overrides your requester. Claims end with your session; take one down earlier with alp_unpin.',
215
216
 
216
- ...(!parentAgent ? ['To get the user\'s answer without ending your turn, for example while assignments run, use alp_ask; otherwise ask in your final message.'] : []),
217
+ ...(!parentAgent ? ['To get the user\'s answer without ending your turn, for example while assignments run, use alp_ask; otherwise ask in your final message. ' +
218
+ 'Tools that hold your turn: alp_ask until the user answers or writes in the chat (at most 30 minutes); alp_delegate with wait: true until the result, the child\'s first question, a check-in or the user\'s message; alp_wait until mail, a check-in, the user\'s message or its timeout (default 5, at most 15 minutes). Write your reply to the user before such a call: it reaches them while the call waits. ' +
219
+ 'Approvals do not hold it: alp_issue create/comment and alp_skill return pending_approval with an approvalId at once, and the decision arrives by mail. Ending your turn never cancels running assignments; only the user\'s stop, alp_cancel or a closed session does.'] : []),
217
220
 
218
221
  // ALPD §54: a root session talks with the user; an assignment only asks them through alp_ask.
219
222
  languageInstruction(mapping.language, mapping.agent.name, !parentAgent),
@@ -250,7 +253,7 @@ export function nativeSessionConfig(
250
253
  type: 'function',
251
254
  name: 'alp_delegate',
252
255
  description:
253
- 'Delegate a bounded task to an authorized ALP agent and wait for its real handoff.',
256
+ 'Delegate a bounded task to an authorized ALP agent. Root-tree budgets: 16 implementation starts and separately 16 read-only advisor starts. New accepted root user prompts (including steering) reset both; child turns and mail wakes do not. The default returns immediately; wait true waits for handoff.',
254
257
  inputSchema: {
255
258
  type: 'object',
256
259
  properties: {
@@ -12,6 +12,8 @@ export type Session = {
12
12
  mapping: ResolvedSession;
13
13
  threadId: string;
14
14
  active?: string;
15
+ /** A continued turn accepts a simultaneous user auto prompt as steering. */
16
+ resumedTurn?: string;
15
17
 
16
18
  pending: boolean;
17
19
  buffered: Array<[string, any]>;
@@ -32,13 +34,18 @@ export type Session = {
32
34
  peerCount: number;
33
35
  /** Live assignments this session requested, keyed by child session id. */
34
36
  assignments: Map<string, Assignment>;
37
+ /** Root-tree implementation starts since the last new root user turn. */
35
38
  calls: number;
39
+ /** Separate finite budget for mandatory read-only reviewer/oracle starts. */
40
+ advisorCalls: number;
36
41
 
37
42
  mail: MailEvent[];
38
43
  waiters: Waiter[];
39
44
  lastActivity: number;
40
- /** Auto-wakes since the last user prompt; capped to stop runaway loops. Check-ins do not count. */
45
+ /** Auto-wakes since the last user prompt or new result/question; check-ins do not count. */
41
46
  wakes: number;
47
+ /** Failed wake starts since fresh result/question/check-in mail or a user prompt. */
48
+ wakeFailures: number;
42
49
  /** When it last got a check-in, or when its assignments started running. */
43
50
  checkInAt?: number;
44
51
  /** Set by interrupt: pending mail waits for the next user prompt. */
@@ -327,7 +327,10 @@ export type CodexToolCall = { name: string; status: 'running' | 'completed' | 'f
327
327
  /** `--- a/<path>` / `+++ b/<path>` headers on a file's diff that lacks them. */
328
328
  function withHeaders(path: string, diff: string) {
329
329
  // Only a diff that starts with its header has one: a removed `-- x` line inside is not it.
330
- return /^(--- |diff --git )/.test(diff) ? diff : `--- a/${path}\n+++ b/${path}\n${diff}`;
330
+ if (/^(--- |diff --git )/.test(diff)) return diff;
331
+ // A diff that starts with its target header lacks only the source one (#117).
332
+ if (diff.startsWith('+++ ')) return `--- a/${path}\n${diff}`;
333
+ return `--- a/${path}\n+++ b/${path}\n${diff}`;
331
334
  }
332
335
 
333
336
  /** Text that starts as a unified diff does: a file header or a hunk (a line inside content does not count). */
package/src/tools.ts CHANGED
@@ -67,7 +67,7 @@ export const HANDOFF_TOOL = {
67
67
  export const WAIT_TOOL = {
68
68
  type: 'function',
69
69
  name: 'alp_wait',
70
- description: 'Wait for mail from your assignments: results, questions, notes, and stall reports. Returns as soon as any arrives, or with an empty list and a status snapshot on timeout.',
70
+ description: 'Wait for mail from your assignments: results, questions, notes, and stall reports. Returns as soon as any arrives, a check-in comes or the user writes to you; otherwise with an empty list and a status snapshot after timeoutMs (default 5 minutes, at most 15). Ending your turn instead never cancels your assignments.',
71
71
  inputSchema: {
72
72
  type: 'object',
73
73
  properties: {
@@ -209,7 +209,7 @@ export const UNPIN_TOOL = {
209
209
  export const ASK_TOOL = {
210
210
  type: 'function',
211
211
  name: 'alp_ask',
212
- description: 'Ask a question and wait for the answer: your requester, or the user. Only main talks to the user, unless the user has written to you. Returns unanswered after the ask timeout.',
212
+ description: 'Ask a question and wait for the answer: your requester, or the user. Only main talks to the user, unless the user has written to you. Holds your turn until answered; returns unanswered after the ask timeout (15 minutes for a requester, 30 for the user), and user_wrote when the user writes in the chat instead of answering.',
213
213
  inputSchema: {
214
214
  type: 'object',
215
215
  properties: {
@@ -240,7 +240,7 @@ export const LESSON_TOOL = {
240
240
  export const SKILL_TOOL = {
241
241
  type: 'function',
242
242
  name: 'alp_skill',
243
- description: 'Propose a skill distilled from your lessons. ALP shows the whole skill to the user, and saves it to the user\'s skill library and gives it to the roles only when the user approves; any other answer comes back as feedback.',
243
+ description: 'Propose a skill distilled from your lessons. ALP shows the whole skill to the user, and saves it to the user\'s skill library and gives it to the roles only when the user approves. Returns pending_approval with approvalId immediately; the decision (saved, or not saved with the user\'s feedback) arrives once by mail, even after your turn ends. Continue other work. A retry of the same draft returns its existing pending/result in this session; use a fresh requestId only to propose an identical draft again.',
244
244
  inputSchema: {
245
245
  type: 'object',
246
246
  properties: {
@@ -250,6 +250,7 @@ export const SKILL_TOOL = {
250
250
  roles: { type: 'array', items: { type: 'string' }, description: 'The roles whose work the skill guides, and only those: any of main (yourself), lead, peer, oracle, reviewer, supervisor, or a custom agent of this project.' },
251
251
  lessons: { type: 'array', items: { type: 'string' }, description: 'The exact text of the lessons this skill replaces; they leave the lessons files once it is saved.' },
252
252
  replace: { type: 'boolean', description: 'Propose a new version of an existing skill of this name.' },
253
+ requestId: { type: 'string', description: 'Optional session-scoped idempotency key (1-100 characters). Reuse with the same draft on retries; a fresh key explicitly proposes a new operation.' },
253
254
  },
254
255
  required: ['name', 'description', 'body', 'roles'],
255
256
  additionalProperties: false,
@@ -259,7 +260,7 @@ export const SKILL_TOOL = {
259
260
  export const ISSUE_TOOL = {
260
261
  type: 'function',
261
262
  name: 'alp_issue',
262
- description: 'GitHub issues of this project (target project, from its origin remote) or of ALP itself (target alp). search needs no approval. create and comment show the whole draft to the user and post it only when the user approves; any other answer comes back as feedback.',
263
+ description: 'GitHub issues of this project (target project, from its origin remote) or of ALP itself (target alp). search needs no approval. create and comment show the whole draft to the user and post it only when the user approves; create/comment return pending_approval with approvalId immediately, then send one decision/posting result by mail, even after your turn ends. Continue other work. Draft retries return the existing pending/result in this session. Use a fresh requestId only to intentionally propose an identical draft again; reuse one requestId with the exact draft to replay safely.',
263
264
  inputSchema: {
264
265
  type: 'object',
265
266
  properties: {
@@ -269,6 +270,7 @@ export const ISSUE_TOOL = {
269
270
  title: { type: 'string', description: 'For create.' },
270
271
  body: { type: 'string', description: 'For create and comment: Markdown. Facts, reproduction and evidence; no secrets.' },
271
272
  issue: { type: 'integer', description: 'For comment: the issue number.' },
273
+ requestId: { type: 'string', description: 'Optional session-scoped idempotency key (1-100 characters). Reuse with the same draft on retries; a fresh key explicitly proposes a new operation.' },
272
274
  labels: { type: 'array', items: { type: 'string' }, description: 'For create: existing labels of the repository.' },
273
275
  },
274
276
  required: ['action', 'target'],