@alisio/core 0.1.0-alpha.9 → 0.1.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.
Files changed (187) hide show
  1. package/README.md +52 -7
  2. package/dist/agents/active.d.ts +99 -0
  3. package/dist/agents/active.js +218 -0
  4. package/dist/agents/capabilities.d.ts +26 -0
  5. package/dist/agents/capabilities.js +120 -0
  6. package/dist/agents/create-agent-skill.d.ts +11 -0
  7. package/dist/agents/create-agent-skill.js +60 -0
  8. package/dist/agents/definitions.d.ts +88 -0
  9. package/dist/agents/definitions.js +404 -0
  10. package/dist/agents/draft.d.ts +41 -0
  11. package/dist/agents/draft.js +133 -0
  12. package/dist/agents/service.d.ts +57 -0
  13. package/dist/agents/service.js +86 -0
  14. package/dist/agents/templates.d.ts +8 -0
  15. package/dist/agents/templates.js +102 -0
  16. package/dist/analysis/capabilities.d.ts +48 -0
  17. package/dist/analysis/capabilities.js +90 -0
  18. package/dist/analysis/data/csv.d.ts +46 -0
  19. package/dist/analysis/data/csv.js +240 -0
  20. package/dist/analysis/data/datasets.d.ts +132 -0
  21. package/dist/analysis/data/datasets.js +441 -0
  22. package/dist/analysis/data/describe.d.ts +27 -0
  23. package/dist/analysis/data/describe.js +103 -0
  24. package/dist/analysis/data/engine-client.d.ts +58 -0
  25. package/dist/analysis/data/engine-client.js +304 -0
  26. package/dist/analysis/data/engine-entry.d.ts +1 -0
  27. package/dist/analysis/data/engine-entry.js +59 -0
  28. package/dist/analysis/data/engine-source.d.ts +4 -0
  29. package/dist/analysis/data/engine-source.js +6 -0
  30. package/dist/analysis/data/engine-types.d.ts +86 -0
  31. package/dist/analysis/data/engine-types.js +7 -0
  32. package/dist/analysis/data/infer.d.ts +42 -0
  33. package/dist/analysis/data/infer.js +118 -0
  34. package/dist/analysis/data/ingest-worker.d.ts +17 -0
  35. package/dist/analysis/data/ingest-worker.js +365 -0
  36. package/dist/analysis/data/json.d.ts +19 -0
  37. package/dist/analysis/data/json.js +44 -0
  38. package/dist/analysis/data/query-worker.d.ts +12 -0
  39. package/dist/analysis/data/query-worker.js +75 -0
  40. package/dist/analysis/data/rows.d.ts +39 -0
  41. package/dist/analysis/data/rows.js +120 -0
  42. package/dist/analysis/data/sql-guard.d.ts +20 -0
  43. package/dist/analysis/data/sql-guard.js +129 -0
  44. package/dist/analysis/data/stats.d.ts +18 -0
  45. package/dist/analysis/data/stats.js +77 -0
  46. package/dist/analysis/install-hints.d.ts +33 -0
  47. package/dist/analysis/install-hints.js +144 -0
  48. package/dist/analysis/janitor.d.ts +60 -0
  49. package/dist/analysis/janitor.js +264 -0
  50. package/dist/analysis/jobs.d.ts +94 -0
  51. package/dist/analysis/jobs.js +165 -0
  52. package/dist/analysis/oci.d.ts +129 -0
  53. package/dist/analysis/oci.js +330 -0
  54. package/dist/analysis/python/sources.d.ts +2 -0
  55. package/dist/analysis/python/sources.js +10 -0
  56. package/dist/analysis/requirements.d.ts +32 -0
  57. package/dist/analysis/requirements.js +73 -0
  58. package/dist/analysis/rerun.d.ts +40 -0
  59. package/dist/analysis/rerun.js +136 -0
  60. package/dist/analysis/runtime-manager.d.ts +146 -0
  61. package/dist/analysis/runtime-manager.js +472 -0
  62. package/dist/analysis/status.d.ts +21 -0
  63. package/dist/analysis/status.js +52 -0
  64. package/dist/application.d.ts +105 -3
  65. package/dist/application.js +446 -18
  66. package/dist/artifacts/kinds.d.ts +35 -0
  67. package/dist/artifacts/kinds.js +177 -0
  68. package/dist/artifacts/publisher.d.ts +7 -0
  69. package/dist/artifacts/publisher.js +29 -0
  70. package/dist/artifacts/store.d.ts +119 -0
  71. package/dist/artifacts/store.js +581 -0
  72. package/dist/background/janitor.d.ts +22 -0
  73. package/dist/background/janitor.js +62 -0
  74. package/dist/background/mirror.d.ts +14 -0
  75. package/dist/background/mirror.js +41 -0
  76. package/dist/background/notify.d.ts +58 -0
  77. package/dist/background/notify.js +172 -0
  78. package/dist/background/output.d.ts +46 -0
  79. package/dist/background/output.js +179 -0
  80. package/dist/background/service.d.ts +137 -0
  81. package/dist/background/service.js +410 -0
  82. package/dist/background/store.d.ts +103 -0
  83. package/dist/background/store.js +195 -0
  84. package/dist/changelog/data.d.ts +6 -0
  85. package/dist/changelog/data.js +306 -0
  86. package/dist/changelog/index.d.ts +5 -0
  87. package/dist/changelog/index.js +7 -0
  88. package/dist/changelog/parse.d.ts +53 -0
  89. package/dist/changelog/parse.js +160 -0
  90. package/dist/commands/builtins.d.ts +10 -0
  91. package/dist/commands/builtins.js +161 -0
  92. package/dist/commands/catalog.d.ts +86 -0
  93. package/dist/commands/catalog.js +304 -0
  94. package/dist/config.d.ts +206 -6
  95. package/dist/config.js +426 -20
  96. package/dist/core/compaction.js +13 -2
  97. package/dist/core/contracts.d.ts +180 -3
  98. package/dist/core/opt-in.d.ts +12 -0
  99. package/dist/core/opt-in.js +12 -0
  100. package/dist/core/output-limit.d.ts +34 -0
  101. package/dist/core/output-limit.js +39 -0
  102. package/dist/core/run-stats.d.ts +16 -0
  103. package/dist/core/run-stats.js +8 -0
  104. package/dist/core/runner.d.ts +134 -4
  105. package/dist/core/runner.js +780 -138
  106. package/dist/core/timeout.d.ts +15 -0
  107. package/dist/core/timeout.js +61 -0
  108. package/dist/core/truncation.d.ts +30 -0
  109. package/dist/core/truncation.js +83 -0
  110. package/dist/goal/blockers.d.ts +45 -0
  111. package/dist/goal/blockers.js +51 -0
  112. package/dist/goal/budget.d.ts +24 -0
  113. package/dist/goal/budget.js +53 -0
  114. package/dist/goal/command.d.ts +36 -0
  115. package/dist/goal/command.js +98 -0
  116. package/dist/goal/format.d.ts +19 -0
  117. package/dist/goal/format.js +102 -0
  118. package/dist/goal/host.d.ts +29 -0
  119. package/dist/goal/host.js +42 -0
  120. package/dist/goal/index.d.ts +9 -0
  121. package/dist/goal/index.js +9 -0
  122. package/dist/goal/machine.d.ts +186 -0
  123. package/dist/goal/machine.js +344 -0
  124. package/dist/goal/prompts.d.ts +10 -0
  125. package/dist/goal/prompts.js +75 -0
  126. package/dist/goal/service.d.ts +113 -0
  127. package/dist/goal/service.js +273 -0
  128. package/dist/goal/store.d.ts +59 -0
  129. package/dist/goal/store.js +184 -0
  130. package/dist/index.d.ts +50 -5
  131. package/dist/index.js +49 -4
  132. package/dist/mcp/connector.js +38 -6
  133. package/dist/mcp/rich.d.ts +41 -0
  134. package/dist/mcp/rich.js +362 -0
  135. package/dist/permissions/modes.d.ts +72 -0
  136. package/dist/permissions/modes.js +98 -0
  137. package/dist/plan/exit-plan.d.ts +25 -0
  138. package/dist/plan/exit-plan.js +130 -0
  139. package/dist/plan/state.d.ts +102 -0
  140. package/dist/plan/state.js +165 -0
  141. package/dist/plugins/host.d.ts +34 -1
  142. package/dist/plugins/host.js +143 -9
  143. package/dist/providers/settings.d.ts +15 -0
  144. package/dist/providers/settings.js +38 -0
  145. package/dist/reload.d.ts +75 -0
  146. package/dist/reload.js +204 -0
  147. package/dist/resources/context.js +6 -1
  148. package/dist/runtime/access.d.ts +62 -0
  149. package/dist/runtime/access.js +115 -0
  150. package/dist/runtime/blobs.d.ts +51 -0
  151. package/dist/runtime/blobs.js +177 -0
  152. package/dist/runtime/diff.d.ts +23 -0
  153. package/dist/runtime/diff.js +154 -0
  154. package/dist/runtime/ids.d.ts +8 -0
  155. package/dist/runtime/ids.js +32 -0
  156. package/dist/runtime/paths.d.ts +11 -0
  157. package/dist/runtime/paths.js +17 -1
  158. package/dist/runtime/process.d.ts +22 -0
  159. package/dist/runtime/process.js +100 -15
  160. package/dist/runtime/sqlite.d.ts +2 -0
  161. package/dist/runtime/sqlite.js +18 -0
  162. package/dist/runtime/store.d.ts +91 -3
  163. package/dist/runtime/store.js +465 -11
  164. package/dist/runtime/zip.d.ts +24 -0
  165. package/dist/runtime/zip.js +126 -0
  166. package/dist/sessions/children.js +17 -1
  167. package/dist/sessions/side-questions.d.ts +57 -0
  168. package/dist/sessions/side-questions.js +161 -0
  169. package/dist/tools/analysis.d.ts +37 -0
  170. package/dist/tools/analysis.js +573 -0
  171. package/dist/tools/artifacts.d.ts +14 -0
  172. package/dist/tools/artifacts.js +143 -0
  173. package/dist/tools/background.d.ts +7 -0
  174. package/dist/tools/background.js +165 -0
  175. package/dist/tools/data.d.ts +7 -0
  176. package/dist/tools/data.js +160 -0
  177. package/dist/tools/execute.d.ts +5 -0
  178. package/dist/tools/execute.js +15 -3
  179. package/dist/tools/goal.d.ts +19 -0
  180. package/dist/tools/goal.js +126 -0
  181. package/dist/tools/search.d.ts +17 -3
  182. package/dist/tools/search.js +153 -0
  183. package/dist/tools/standard.d.ts +4 -1
  184. package/dist/tools/standard.js +99 -18
  185. package/dist/version.d.ts +5 -3
  186. package/dist/version.js +35 -6
  187. package/package.json +2 -2
@@ -0,0 +1,15 @@
1
+ import type { RunTimeoutInfo } from "@alisio/sdk";
2
+ /** A run stopped by a time limit; the message says which limit, which model and what to do. */
3
+ export declare class RunTimeoutError extends Error {
4
+ readonly info: RunTimeoutInfo;
5
+ constructor(info: RunTimeoutInfo);
6
+ }
7
+ /** `AbortSignal.timeout` aborts with a `TimeoutError`; an explicit `abort()` does not. */
8
+ export declare function isTimeoutReason(reason: unknown): boolean;
9
+ /**
10
+ * A short, human label for a provider id: the host of an OpenAI-compatible endpoint
11
+ * (`openai-compatible:chat:https://api.example.com/v1` → `api.example.com`), else the id itself.
12
+ */
13
+ export declare function providerLabel(id: string | undefined): string | undefined;
14
+ /** Plain-text message of a timeout (TUI, headless, logs, the `runs.error` column). English only. */
15
+ export declare function describeTimeout(info: RunTimeoutInfo): string;
@@ -0,0 +1,61 @@
1
+ /** A run stopped by a time limit; the message says which limit, which model and what to do. */
2
+ export class RunTimeoutError extends Error {
3
+ info;
4
+ constructor(info) {
5
+ super(describeTimeout(info));
6
+ this.name = "RunTimeoutError";
7
+ this.info = info;
8
+ }
9
+ }
10
+ /** `AbortSignal.timeout` aborts with a `TimeoutError`; an explicit `abort()` does not. */
11
+ export function isTimeoutReason(reason) {
12
+ return (typeof reason === "object" &&
13
+ reason !== null &&
14
+ reason.name === "TimeoutError");
15
+ }
16
+ /**
17
+ * A short, human label for a provider id: the host of an OpenAI-compatible endpoint
18
+ * (`openai-compatible:chat:https://api.example.com/v1` → `api.example.com`), else the id itself.
19
+ */
20
+ export function providerLabel(id) {
21
+ if (!id)
22
+ return undefined;
23
+ const url = /(https?:\/\/[^\s]+)/.exec(id)?.[1];
24
+ if (url)
25
+ try {
26
+ return new URL(url).host;
27
+ }
28
+ catch {
29
+ /* fall through to the raw id */
30
+ }
31
+ return id;
32
+ }
33
+ const seconds = (ms) => Math.round(ms / 1000);
34
+ /** Plain-text message of a timeout (TUI, headless, logs, the `runs.error` column). English only. */
35
+ export function describeTimeout(info) {
36
+ const who = info.model
37
+ ? `The model ${info.model}${info.provider ? ` (${info.provider})` : ""}`
38
+ : "The model";
39
+ const limit = `${seconds(info.ms)} s`;
40
+ if (info.kind === "first_token" && info.attempts && info.attempts > 1)
41
+ return (`${who} did not respond after ${info.attempts} attempts of ${limit} each: no tokens were received, so the run was stopped. ` +
42
+ "Retry, switch to another model, or raise or disable limits.firstTokenTimeoutMs (0 disables it); " +
43
+ "limits.firstTokenRetries sets how many times a silent request is sent again.");
44
+ if (info.kind === "first_token")
45
+ return (`${who} sent nothing within ${limit} of the request, so the run was stopped. ` +
46
+ "Retry, switch to another model, or raise or disable limits.firstTokenTimeoutMs (0 disables it).");
47
+ if (info.stage === "waiting_model")
48
+ return info.firstRequest
49
+ ? `${who} did not respond within ${limit}: no tokens were received. ` +
50
+ "Retry, switch to another model, or raise limits.timeoutMs."
51
+ : `The run reached its ${limit} limit while waiting for ${info.model ? `the model ${info.model}` : "the model"}` +
52
+ `${info.provider ? ` (${info.provider})` : ""}. ` +
53
+ "Everything produced so far is kept: prompt again to continue, switch model, or raise limits.timeoutMs.";
54
+ const doing = info.stage === "tool" && info.tool
55
+ ? `while running ${info.tool}`
56
+ : info.stage === "streaming"
57
+ ? "while the model was answering"
58
+ : "";
59
+ return (`The run reached its ${limit} limit${doing ? ` ${doing}` : ""}. ` +
60
+ "Everything produced so far is kept: prompt again to continue, or raise limits.timeoutMs.");
61
+ }
@@ -0,0 +1,30 @@
1
+ import { type RunTruncationInfo, type ToolCall } from "@alisio/sdk";
2
+ /**
3
+ * Recovery from a model response that the output-token limit (`limits.maxOutputTokens`) cut
4
+ * off before it was usable. Provider-agnostic: the runner decides from the completed message
5
+ * (`truncated: true`) or from the typed `OutputTruncatedError` signal, never from SDK types.
6
+ */
7
+ /** The run failed because every response was cut off; carries the numbers for the UIs. */
8
+ export declare class RunTruncationError extends Error {
9
+ readonly code = "output_truncated";
10
+ readonly info: RunTruncationInfo;
11
+ constructor(info: RunTruncationInfo);
12
+ }
13
+ /** Plain-text failure message (TUI, headless, logs). English only; the web localizes it. */
14
+ export declare function describeTruncation(info: RunTruncationInfo): string;
15
+ /** Whether a provider failure means "the response was cut off by the output-token limit". */
16
+ export declare function isOutputTruncationError(error: unknown): boolean;
17
+ /**
18
+ * Whether a tool call of a truncated response cannot be trusted: missing id or name, or
19
+ * arguments that are not a complete JSON object. Empty arguments only count as incomplete for
20
+ * the LAST call (the one being streamed when the limit hit); an earlier call with no arguments
21
+ * is how a provider reports a call without parameters.
22
+ */
23
+ export declare function incompleteCalls(calls: ToolCall[]): boolean;
24
+ /** What the model is told on the request that follows a truncated response. */
25
+ export declare const TRUNCATION_NOTICE: string;
26
+ /**
27
+ * One step below `current` among the model's supported levels, or `undefined` when that cannot
28
+ * be decided cleanly (unknown level names, no current level, or already the lowest).
29
+ */
30
+ export declare function lowerEffort(current: string | undefined, supported: readonly string[] | undefined): string | undefined;
@@ -0,0 +1,83 @@
1
+ import { OUTPUT_TRUNCATED_CODE } from "@alisio/sdk";
2
+ import { describeOutputLimitSource } from "./output-limit.js";
3
+ /**
4
+ * Recovery from a model response that the output-token limit (`limits.maxOutputTokens`) cut
5
+ * off before it was usable. Provider-agnostic: the runner decides from the completed message
6
+ * (`truncated: true`) or from the typed `OutputTruncatedError` signal, never from SDK types.
7
+ */
8
+ /** The run failed because every response was cut off; carries the numbers for the UIs. */
9
+ export class RunTruncationError extends Error {
10
+ code = OUTPUT_TRUNCATED_CODE;
11
+ info;
12
+ constructor(info) {
13
+ super(describeTruncation(info));
14
+ this.name = "RunTruncationError";
15
+ this.info = info;
16
+ }
17
+ }
18
+ /** Plain-text failure message (TUI, headless, logs). English only; the web localizes it. */
19
+ export function describeTruncation(info) {
20
+ const who = info.model ? `The model ${info.model}` : "The model";
21
+ const times = info.attempts === 1 ? "once" : `${info.attempts} times`;
22
+ return (`${who} was cut off ${times} by the output-token limit (${info.maxOutputTokens} tokens, ${describeOutputLimitSource(info.source)}) ` +
23
+ "before it produced a usable response, so the run was stopped. " +
24
+ "Raise limits.maxOutputTokens (/settings or the config file), lower the reasoning effort (/effort), " +
25
+ "or ask for the result in smaller parts.");
26
+ }
27
+ /**
28
+ * COMPATIBILITY: provider plugins published before the typed signal (for example
29
+ * `@alisio/plugin-deepseek` 0.1.1) throw a plain `Error` with this text when a response is cut
30
+ * off before any visible text. It is the only place the text is matched; new providers should
31
+ * yield `completed` with `truncated: true` or throw `OutputTruncatedError` from `@alisio/sdk`.
32
+ */
33
+ const LEGACY_TRUNCATION_TEXT = "cut off by max output tokens before any usable content";
34
+ /** Whether a provider failure means "the response was cut off by the output-token limit". */
35
+ export function isOutputTruncationError(error) {
36
+ if (typeof error !== "object" || error === null)
37
+ return false;
38
+ const { code, message } = error;
39
+ return (code === OUTPUT_TRUNCATED_CODE ||
40
+ (typeof message === "string" && message.includes(LEGACY_TRUNCATION_TEXT)));
41
+ }
42
+ /**
43
+ * Whether a tool call of a truncated response cannot be trusted: missing id or name, or
44
+ * arguments that are not a complete JSON object. Empty arguments only count as incomplete for
45
+ * the LAST call (the one being streamed when the limit hit); an earlier call with no arguments
46
+ * is how a provider reports a call without parameters.
47
+ */
48
+ export function incompleteCalls(calls) {
49
+ return calls.some((call, index) => {
50
+ if (!call.id || !call.name)
51
+ return true;
52
+ const raw = call.arguments.trim();
53
+ if (!raw)
54
+ return index === calls.length - 1;
55
+ try {
56
+ const parsed = JSON.parse(raw);
57
+ return typeof parsed !== "object" || parsed === null || Array.isArray(parsed);
58
+ }
59
+ catch {
60
+ return true;
61
+ }
62
+ });
63
+ }
64
+ /** What the model is told on the request that follows a truncated response. */
65
+ export const TRUNCATION_NOTICE = "Your previous reply was cut off by the output-token limit, so it was discarded. " +
66
+ "Do not repeat what you already produced. Work in smaller steps: write long files in several parts " +
67
+ "(for example save the script or data to files in several tool calls, or build the dashboard with " +
68
+ "the compact alisio_runtime.html and svg helpers), keep your reasoning short, and continue from where you stopped.";
69
+ const EFFORT_RANK = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
70
+ /**
71
+ * One step below `current` among the model's supported levels, or `undefined` when that cannot
72
+ * be decided cleanly (unknown level names, no current level, or already the lowest).
73
+ */
74
+ export function lowerEffort(current, supported) {
75
+ if (!current || !supported?.length)
76
+ return undefined;
77
+ const rank = (level) => EFFORT_RANK.indexOf(level);
78
+ if (rank(current) < 0 || supported.some((level) => rank(level) < 0))
79
+ return undefined;
80
+ const lower = [...supported].filter((level) => rank(level) < rank(current));
81
+ lower.sort((a, b) => rank(b) - rank(a));
82
+ return lower[0];
83
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The ONE ordered list of reasons a goal does not continue right now. Hosts (TUI, server) build
3
+ * a `GoalHostState` from what they can see and call `goalBlocker`; nothing else decides. A
4
+ * blocker is a WAIT, not a state change: the goal stays `active` and continues when the cause is
5
+ * gone (the host re-checks on the events that clear it).
6
+ *
7
+ * The order matters (the first match wins, and it is what the UI shows):
8
+ * 1. not active - nothing to continue (paused, blocked, budget_limited, complete, none)
9
+ * 2. disabled - `goal.enabled` is false
10
+ * 3. busy - a run of the session is in flight, or a continuation is already claimed
11
+ * 4. queued_input - the user has input queued: the user always goes first
12
+ * 5. approval - a permission request is waiting for the user
13
+ * 6. question - a question (or a plan review) is waiting for the user
14
+ * 7. plan_mode - the active agent is the plan agent
15
+ * 8. background_tasks - background tasks of the session are still running
16
+ */
17
+ import type { GoalStatus, GoalWaiting } from "@alisio/sdk";
18
+ export interface GoalHostState {
19
+ /** The goal's status, or undefined when the session has none. */
20
+ status: GoalStatus | undefined;
21
+ enabled: boolean;
22
+ /** A run is executing (or queued) for the session, or another process holds the session. */
23
+ running: boolean;
24
+ /** A continuation was claimed and its run has not settled yet. */
25
+ inflight: boolean;
26
+ /** The user has typed or queued something that has not been sent yet. */
27
+ queuedInput: boolean;
28
+ /** What is waiting for the user, if anything. */
29
+ awaiting?: "approval" | "question";
30
+ /** The agent that would run is the plan agent. */
31
+ planMode: boolean;
32
+ /** Live background tasks of the session. */
33
+ liveTasks: number;
34
+ }
35
+ export type GoalBlocker = "not_active" | "disabled" | "busy" | "queued_input" | "approval" | "question" | "plan_mode" | "background_tasks";
36
+ /** The first reason the goal must not continue now, or undefined when it may. */
37
+ export declare function goalBlocker(state: GoalHostState): GoalBlocker | undefined;
38
+ /** What the UI shows as "waiting for…" (a busy session is working, not waiting). */
39
+ export declare function waitingOf(blocker: GoalBlocker | undefined): GoalWaiting | undefined;
40
+ /**
41
+ * The label a UI shows for an active goal. A question or approval is shown even while the run
42
+ * that asked it is still going (the run is what waits); the other waits only count once the
43
+ * session is idle (a task running while the agent works is not "waiting").
44
+ */
45
+ export declare function goalWaiting(state: GoalHostState): GoalWaiting | undefined;
@@ -0,0 +1,51 @@
1
+ /** The first reason the goal must not continue now, or undefined when it may. */
2
+ export function goalBlocker(state) {
3
+ if (state.status !== "active")
4
+ return "not_active";
5
+ if (!state.enabled)
6
+ return "disabled";
7
+ if (state.running || state.inflight)
8
+ return "busy";
9
+ if (state.queuedInput)
10
+ return "queued_input";
11
+ if (state.awaiting === "approval")
12
+ return "approval";
13
+ if (state.awaiting === "question")
14
+ return "question";
15
+ if (state.planMode)
16
+ return "plan_mode";
17
+ if (state.liveTasks > 0)
18
+ return "background_tasks";
19
+ return undefined;
20
+ }
21
+ /** What the UI shows as "waiting for…" (a busy session is working, not waiting). */
22
+ export function waitingOf(blocker) {
23
+ switch (blocker) {
24
+ case "approval":
25
+ return "approval";
26
+ case "question":
27
+ return "question";
28
+ case "queued_input":
29
+ return "user_input";
30
+ case "plan_mode":
31
+ return "plan_mode";
32
+ case "background_tasks":
33
+ return "background_tasks";
34
+ default:
35
+ return undefined;
36
+ }
37
+ }
38
+ /**
39
+ * The label a UI shows for an active goal. A question or approval is shown even while the run
40
+ * that asked it is still going (the run is what waits); the other waits only count once the
41
+ * session is idle (a task running while the agent works is not "waiting").
42
+ */
43
+ export function goalWaiting(state) {
44
+ if (state.status !== "active" || !state.enabled)
45
+ return undefined;
46
+ if (state.awaiting)
47
+ return state.awaiting;
48
+ if (state.running || state.inflight)
49
+ return undefined;
50
+ return waitingOf(goalBlocker(state));
51
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Token budget syntax of `/goal` (`budget=50k`, `budget=1.5M`, `budget=clear`). Pure.
3
+ *
4
+ * A budget is a number of tokens as the runner counts them: the input AND output tokens of every
5
+ * request of every run of the goal (so a long context is paid again on each turn).
6
+ */
7
+ /** The values that remove the budget. */
8
+ export declare const BUDGET_CLEAR_WORDS: ReadonlySet<string>;
9
+ /** The largest budget accepted (a typo guard: one billion tokens). */
10
+ export declare const MAX_TOKEN_BUDGET = 1000000000;
11
+ export type BudgetParse = {
12
+ ok: true;
13
+ tokens: number | null;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ };
18
+ /**
19
+ * `50k`, `1.5M`, `2m`, `50000` or a clear word. `tokens: null` clears the budget. Decimals are
20
+ * allowed with a suffix only (`1.5M`); the result is a positive integer.
21
+ */
22
+ export declare function parseTokenBudget(raw: string): BudgetParse;
23
+ /** `1.5M`, `50k`, `812` (the inverse of the parser, for UIs). */
24
+ export declare function formatTokenCount(tokens: number): string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Token budget syntax of `/goal` (`budget=50k`, `budget=1.5M`, `budget=clear`). Pure.
3
+ *
4
+ * A budget is a number of tokens as the runner counts them: the input AND output tokens of every
5
+ * request of every run of the goal (so a long context is paid again on each turn).
6
+ */
7
+ /** The values that remove the budget. */
8
+ export const BUDGET_CLEAR_WORDS = new Set(["clear", "none", "off", "0"]);
9
+ /** The largest budget accepted (a typo guard: one billion tokens). */
10
+ export const MAX_TOKEN_BUDGET = 1_000_000_000;
11
+ const SUFFIX = { k: 1_000, m: 1_000_000, b: 1_000_000_000 };
12
+ /**
13
+ * `50k`, `1.5M`, `2m`, `50000` or a clear word. `tokens: null` clears the budget. Decimals are
14
+ * allowed with a suffix only (`1.5M`); the result is a positive integer.
15
+ */
16
+ export function parseTokenBudget(raw) {
17
+ const text = raw.trim().toLowerCase().replace(/_/g, "");
18
+ if (!text)
19
+ return { ok: false, error: "A budget is empty: use e.g. budget=50k or budget=clear." };
20
+ if (BUDGET_CLEAR_WORDS.has(text))
21
+ return { ok: true, tokens: null };
22
+ const match = /^(\d+(?:\.\d+)?)([kmb])?$/.exec(text);
23
+ if (!match)
24
+ return { ok: false, error: `"${raw}" is not a token budget: use e.g. 50k, 1.5M or clear.` };
25
+ const [, digits = "", suffix] = match;
26
+ if (!suffix && digits.includes("."))
27
+ return {
28
+ ok: false,
29
+ error: `"${raw}" is not a token budget: decimals need a K or M suffix (1.5M).`,
30
+ };
31
+ const tokens = Math.round(Number(digits) * (suffix ? (SUFFIX[suffix] ?? 1) : 1));
32
+ if (!Number.isFinite(tokens) || tokens < 1)
33
+ return {
34
+ ok: false,
35
+ error: `"${raw}" is not a token budget: it must be at least 1 token (or clear).`,
36
+ };
37
+ if (tokens > MAX_TOKEN_BUDGET)
38
+ return { ok: false, error: `"${raw}" is above the maximum budget of 1B tokens.` };
39
+ return { ok: true, tokens };
40
+ }
41
+ /** `1.5M`, `50k`, `812` (the inverse of the parser, for UIs). */
42
+ export function formatTokenCount(tokens) {
43
+ if (!Number.isFinite(tokens) || tokens < 0)
44
+ return "0";
45
+ if (tokens < 1_000)
46
+ return String(Math.round(tokens));
47
+ const trim = (value) => value.replace(/\.0$/, "");
48
+ if (tokens < 1_000_000) {
49
+ const k = tokens / 1_000;
50
+ return `${k < 100 ? trim(k.toFixed(1)) : Math.round(k)}k`;
51
+ }
52
+ return `${trim((tokens / 1_000_000).toFixed(1))}M`;
53
+ }
@@ -0,0 +1,36 @@
1
+ /** The longest objective accepted, in characters (it is sent to the model on the kickoff). */
2
+ export declare const GOAL_OBJECTIVE_MAX = 4000;
3
+ export declare const GOAL_SUBCOMMANDS: readonly ["pause", "resume", "edit", "clear", "help"];
4
+ export type GoalCommand = {
5
+ type: "show";
6
+ } | {
7
+ type: "help";
8
+ } | {
9
+ type: "pause";
10
+ } | {
11
+ type: "resume";
12
+ } | {
13
+ type: "clear";
14
+ }
15
+ /** `objective` is absent when the UI should ask for the new text. */
16
+ | {
17
+ type: "edit";
18
+ objective?: string;
19
+ } | {
20
+ type: "budget";
21
+ tokens: number | null;
22
+ } | {
23
+ type: "create";
24
+ objective: string;
25
+ tokenBudget?: number;
26
+ } | {
27
+ type: "error";
28
+ message: string;
29
+ };
30
+ export declare const GOAL_USAGE = "Usage: /goal [<objective> [budget=50k]] | pause | resume | edit [<objective>] | clear | budget=<n|clear> | help";
31
+ /** Markdown help shown by `/goal help` (identical on every surface). */
32
+ export declare const GOAL_HELP: string;
33
+ /** Words of `args` split on whitespace, keeping the original spacing of the objective. */
34
+ export declare function parseGoalCommand(args: string): GoalCommand;
35
+ /** Why an objective is not acceptable (the same rule for the command and the API), if so. */
36
+ export declare function objectiveError(objective: string): string | undefined;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The `/goal` command line, parsed once for every surface (TUI, web, API). Pure.
3
+ *
4
+ * /goal show the current goal
5
+ * /goal <objective> [budget=N] create (or replace) the goal and start working on it
6
+ * /goal pause | resume | clear | help
7
+ * /goal edit [<objective>] change the objective of the current goal
8
+ * /goal budget=50k set the token budget of the current goal (budget=clear removes it)
9
+ */
10
+ import { parseTokenBudget } from "./budget.js";
11
+ /** The longest objective accepted, in characters (it is sent to the model on the kickoff). */
12
+ export const GOAL_OBJECTIVE_MAX = 4000;
13
+ export const GOAL_SUBCOMMANDS = ["pause", "resume", "edit", "clear", "help"];
14
+ const BUDGET_WORD = /^budget=(.*)$/i;
15
+ export const GOAL_USAGE = "Usage: /goal [<objective> [budget=50k]] | pause | resume | edit [<objective>] | clear | budget=<n|clear> | help";
16
+ /** Markdown help shown by `/goal help` (identical on every surface). */
17
+ export const GOAL_HELP = [
18
+ "**/goal** keeps the agent working on one objective until it is done, blocked, paused or out of budget.",
19
+ "",
20
+ "- `/goal` shows the current goal.",
21
+ "- `/goal <objective>` starts a goal (one per session; an existing goal is replaced after you confirm).",
22
+ "- `/goal <objective> budget=50k` also sets a token budget (`50k`, `1.5M`, `250000`).",
23
+ "- `/goal pause` stops the automatic continuation; `/goal resume` continues a paused or blocked goal.",
24
+ "- `/goal edit <objective>` changes the objective; `/goal clear` removes the goal.",
25
+ "- `/goal budget=50k` sets the token budget; `/goal budget=clear` (also `none`, `off`, `0`) removes it.",
26
+ "",
27
+ `The objective can be up to ${GOAL_OBJECTIVE_MAX} characters. Only you pause, resume, edit or clear a goal or change its budget; the agent marks it complete or blocked, with evidence.`,
28
+ "The goal never widens permissions: approvals work as in a normal turn, and it never runs in plan mode.",
29
+ "A goal without a token budget is limited only by its turn and time caps (`goal.maxTurns`, `goal.maxMinutes`).",
30
+ ].join("\n");
31
+ /** Words of `args` split on whitespace, keeping the original spacing of the objective. */
32
+ export function parseGoalCommand(args) {
33
+ const text = args.trim();
34
+ if (!text)
35
+ return { type: "show" };
36
+ const words = text.split(/\s+/);
37
+ // `budget=` words (anywhere) are options, never part of the objective.
38
+ const budgets = words.filter((word) => BUDGET_WORD.test(word));
39
+ if (budgets.length > 1)
40
+ return { type: "error", message: "Give only one budget=: it appears more than once." };
41
+ let tokens;
42
+ if (budgets[0]) {
43
+ const parsed = parseTokenBudget(BUDGET_WORD.exec(budgets[0])?.[1] ?? "");
44
+ if (!parsed.ok)
45
+ return { type: "error", message: parsed.error };
46
+ tokens = parsed.tokens;
47
+ }
48
+ const rest = words.filter((word) => !BUDGET_WORD.test(word));
49
+ const first = rest[0]?.toLowerCase();
50
+ const subcommand = GOAL_SUBCOMMANDS.find((name) => name === first);
51
+ if (!rest.length)
52
+ return { type: "budget", tokens: tokens ?? null };
53
+ if (subcommand) {
54
+ if (tokens !== undefined)
55
+ return {
56
+ type: "error",
57
+ message: `budget= goes with a new objective or alone (/goal budget=50k), not with /goal ${subcommand}.`,
58
+ };
59
+ if (subcommand === "edit") {
60
+ const objective = text.slice(text.search(/\s/) + 1).trim();
61
+ if (rest.length === 1)
62
+ return { type: "edit" };
63
+ return validateObjective(objective, (value) => ({ type: "edit", objective: value }));
64
+ }
65
+ if (rest.length > 1)
66
+ return {
67
+ type: "error",
68
+ message: `/goal ${subcommand} takes no arguments. To start a goal whose objective begins with "${subcommand}", reword it.`,
69
+ };
70
+ return { type: subcommand };
71
+ }
72
+ // The objective keeps the user's own spacing and line breaks; only the budget word is removed.
73
+ const objective = text
74
+ .split(/(\s+)/)
75
+ .filter((part) => !BUDGET_WORD.test(part))
76
+ .join("")
77
+ .trim();
78
+ return validateObjective(objective, (value) => ({
79
+ type: "create",
80
+ objective: value,
81
+ ...(typeof tokens === "number" ? { tokenBudget: tokens } : {}),
82
+ }));
83
+ }
84
+ function validateObjective(objective, build) {
85
+ const error = objectiveError(objective);
86
+ return error ? { type: "error", message: error } : build(objective.trim());
87
+ }
88
+ /** Why an objective is not acceptable (the same rule for the command and the API), if so. */
89
+ export function objectiveError(objective) {
90
+ const value = objective.trim();
91
+ if (!value)
92
+ return "The goal objective is empty.";
93
+ if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value))
94
+ return "The goal objective contains control characters.";
95
+ if (value.length > GOAL_OBJECTIVE_MAX)
96
+ return `The goal objective is too long (${value.length} characters; the limit is ${GOAL_OBJECTIVE_MAX}).`;
97
+ return undefined;
98
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * English wording of goal states, reasons and waits, shared by the TUI and the text output of
3
+ * the API commands. The web UI has its own EN/ES dictionaries for the same codes. Pure.
4
+ */
5
+ import type { GoalAction, GoalInfo, GoalStatus, GoalWaiting } from "@alisio/sdk";
6
+ export declare const GOAL_STATUS_LABEL: Record<GoalStatus, string>;
7
+ export declare const GOAL_WAITING_LABEL: Record<GoalWaiting, string>;
8
+ /** Why the goal is in its state, as one short sentence (empty when there is nothing to say). */
9
+ export declare function goalReasonText(goal: Pick<GoalInfo, "reason" | "detail">): string;
10
+ /** `45s`, `4m 12s`, `1h 05m`. */
11
+ export declare function formatGoalElapsed(ms: number): string;
12
+ /** `12.3k / 50k tokens` or `12.3k tokens (no budget)`. */
13
+ export declare function goalTokensText(goal: Pick<GoalInfo, "tokensUsed" | "tokenBudget">): string;
14
+ /** The action hints of a state, e.g. `/goal pause · /goal edit · /goal clear`. */
15
+ export declare function goalActionHints(actions: readonly GoalAction[], budget?: boolean): string;
16
+ /** `Active · 12.3k / 50k tokens · turn 3/50 · 4m 12s`. */
17
+ export declare function goalSummaryLine(goal: GoalInfo): string;
18
+ /** The status as Markdown (API commands and `/goal` with no arguments). */
19
+ export declare function formatGoalStatus(goal: GoalInfo): string;
@@ -0,0 +1,102 @@
1
+ import { formatTokenCount } from "./budget.js";
2
+ export const GOAL_STATUS_LABEL = {
3
+ active: "Active",
4
+ paused: "Paused",
5
+ blocked: "Blocked",
6
+ budget_limited: "Budget reached",
7
+ complete: "Complete",
8
+ };
9
+ export const GOAL_WAITING_LABEL = {
10
+ approval: "Waiting for permission",
11
+ question: "Waiting for your answer",
12
+ user_input: "Waiting for you to send or clear what you typed",
13
+ plan_mode: "Waiting: plan mode is on (switch to the build agent)",
14
+ background_tasks: "Waiting for background tasks",
15
+ };
16
+ const REASON_TEXT = {
17
+ created: "Started",
18
+ resumed: "Resumed",
19
+ edited: "Objective edited",
20
+ user_paused: "Paused by you",
21
+ user_interrupt: "You interrupted the turn",
22
+ model_complete: "The agent reports the goal complete",
23
+ model_blocked: "The agent reports it cannot continue",
24
+ policy_denied: "A permission was denied",
25
+ run_error: "A turn failed",
26
+ token_budget: "The token budget was reached",
27
+ max_turns: "The turn limit was reached",
28
+ max_wall: "The time limit was reached",
29
+ no_progress: "No progress",
30
+ restart: "Alisio restarted while the goal was running",
31
+ };
32
+ const DETAIL_TEXT = {
33
+ repeated_reply: "the same reply repeated",
34
+ no_tool_turns: "several turns without tool calls",
35
+ };
36
+ /** Why the goal is in its state, as one short sentence (empty when there is nothing to say). */
37
+ export function goalReasonText(goal) {
38
+ if (!goal.reason)
39
+ return "";
40
+ const base = REASON_TEXT[goal.reason];
41
+ if (!goal.detail)
42
+ return base;
43
+ return `${base}: ${DETAIL_TEXT[goal.detail] ?? goal.detail}`;
44
+ }
45
+ /** `45s`, `4m 12s`, `1h 05m`. */
46
+ export function formatGoalElapsed(ms) {
47
+ const total = Math.max(0, Math.round(ms / 1000));
48
+ if (total < 60)
49
+ return `${total}s`;
50
+ const minutes = Math.floor(total / 60);
51
+ if (minutes < 60)
52
+ return `${minutes}m ${String(total % 60).padStart(2, "0")}s`;
53
+ return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, "0")}m`;
54
+ }
55
+ /** `12.3k / 50k tokens` or `12.3k tokens (no budget)`. */
56
+ export function goalTokensText(goal) {
57
+ return goal.tokenBudget !== undefined
58
+ ? `${formatTokenCount(goal.tokensUsed)} / ${formatTokenCount(goal.tokenBudget)} tokens`
59
+ : `${formatTokenCount(goal.tokensUsed)} tokens (no budget)`;
60
+ }
61
+ const ACTION_HINT = {
62
+ pause: "/goal pause",
63
+ resume: "/goal resume",
64
+ edit: "/goal edit",
65
+ clear: "/goal clear",
66
+ budget: "/goal budget=<n>",
67
+ };
68
+ /** The action hints of a state, e.g. `/goal pause · /goal edit · /goal clear`. */
69
+ export function goalActionHints(actions, budget = false) {
70
+ return actions
71
+ .filter((action) => budget || action !== "budget")
72
+ .map((action) => ACTION_HINT[action])
73
+ .join(" · ");
74
+ }
75
+ /** `Active · 12.3k / 50k tokens · turn 3/50 · 4m 12s`. */
76
+ export function goalSummaryLine(goal) {
77
+ return [
78
+ GOAL_STATUS_LABEL[goal.status],
79
+ goalTokensText(goal),
80
+ `turn ${goal.turnsUsed}/${goal.maxTurns}`,
81
+ formatGoalElapsed(goal.activeMs),
82
+ ].join(" · ");
83
+ }
84
+ /** The status as Markdown (API commands and `/goal` with no arguments). */
85
+ export function formatGoalStatus(goal) {
86
+ const lines = [
87
+ `**Goal** · ${goalSummaryLine(goal)}`,
88
+ "",
89
+ `> ${goal.objective.replace(/\n/g, "\n> ")}`,
90
+ ];
91
+ const why = goal.waiting ? GOAL_WAITING_LABEL[goal.waiting] : goalReasonText(goal);
92
+ if (why && !(goal.status === "active" && !goal.waiting))
93
+ lines.push("", why);
94
+ if (goal.summary)
95
+ lines.push("", `Agent's report: ${goal.summary}`);
96
+ if (goal.evidence?.length)
97
+ lines.push("", ...goal.evidence.map((item) => `- ${item.kind}: ${item.detail}`));
98
+ const hints = goalActionHints(goal.actions, true);
99
+ if (hints)
100
+ lines.push("", hints);
101
+ return lines.join("\n");
102
+ }
@@ -0,0 +1,29 @@
1
+ import type { GoalRecord, GoalRunOutcome } from "./machine.ts";
2
+ /** What `runner.run` resolves with (the parts the goal needs). */
3
+ export interface RunSummary {
4
+ text: string;
5
+ status: string;
6
+ usage: {
7
+ input: number;
8
+ output: number;
9
+ };
10
+ toolCalls?: number;
11
+ }
12
+ /**
13
+ * The outcome of one run. `cancelled` is whether the run's abort signal fired (the user stopped
14
+ * it, or the host is shutting down); a failed run carries its error; a provider timeout is flagged
15
+ * so the goal does not block on something the runner already retried.
16
+ */
17
+ export declare function goalOutcome(input: {
18
+ runId: string;
19
+ startedAt: number;
20
+ endedAt: number;
21
+ cancelled: boolean;
22
+ result?: RunSummary;
23
+ error?: unknown;
24
+ }): GoalRunOutcome;
25
+ /**
26
+ * The opt-in tools a run of this session gets: the goal tools while the goal is `active` and the
27
+ * agent is not the plan agent (a plan run never works on a goal).
28
+ */
29
+ export declare function goalOptInTools(goal: GoalRecord | undefined, planMode: boolean): string[];