@xandreed/core 0.13.0 → 0.14.0

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 (148) hide show
  1. package/README.md +1 -1
  2. package/dist/conformance.functions.d.ts +34 -0
  3. package/dist/conformance.functions.js +25 -0
  4. package/dist/domain/bash.entity.d.ts +82 -0
  5. package/dist/domain/bash.entity.functions.d.ts +17 -0
  6. package/dist/domain/bash.entity.functions.js +26 -0
  7. package/dist/domain/bash.entity.functions.test.d.ts +1 -0
  8. package/dist/domain/bash.entity.js +30 -0
  9. package/dist/domain/file-system.entity.d.ts +91 -0
  10. package/dist/domain/file-system.entity.functions.d.ts +18 -0
  11. package/dist/domain/file-system.entity.functions.js +30 -0
  12. package/dist/domain/file-system.entity.js +37 -0
  13. package/dist/domain/loop-event.entity.d.ts +9 -0
  14. package/dist/domain/media.entity.d.ts +79 -0
  15. package/dist/domain/media.entity.functions.d.ts +76 -0
  16. package/dist/domain/media.entity.functions.js +110 -0
  17. package/dist/domain/media.entity.js +38 -0
  18. package/dist/domain/media.entity.test.d.ts +1 -0
  19. package/dist/domain/message.entity.d.ts +157 -5
  20. package/dist/domain/message.entity.js +8 -4
  21. package/dist/domain/model-catalog.entity.d.ts +2 -0
  22. package/dist/domain/model-catalog.entity.js +2 -1
  23. package/dist/domain/token-usage.entity.d.ts +45 -1
  24. package/dist/domain/token-usage.entity.functions.d.ts +30 -1
  25. package/dist/domain/token-usage.entity.functions.js +24 -4
  26. package/dist/domain/token-usage.entity.js +19 -2
  27. package/dist/domain/token-usage.entity.test.d.ts +1 -0
  28. package/dist/harness/capability.entity.functions.js +2 -1
  29. package/dist/harness/plugin.adapter.d.ts +3 -0
  30. package/dist/harness/plugin.adapter.js +7 -1
  31. package/dist/harness/skill-files.entity.d.ts +101 -0
  32. package/dist/harness/skill-files.entity.functions.d.ts +58 -0
  33. package/dist/harness/skill-files.entity.functions.js +150 -0
  34. package/dist/harness/skill-files.entity.functions.test.d.ts +1 -0
  35. package/dist/harness/skill-files.entity.js +25 -0
  36. package/dist/index.d.ts +48 -8
  37. package/dist/index.js +67 -6
  38. package/dist/loop/mapping.d.ts +12 -6
  39. package/dist/loop/mapping.js +30 -6
  40. package/dist/loop/stepContext.d.ts +8 -0
  41. package/dist/loop/stepContext.js +1 -1
  42. package/dist/loop/traceContent.d.ts +29 -0
  43. package/dist/loop/traceContent.js +4 -0
  44. package/dist/loop/traceContent.test.d.ts +1 -0
  45. package/dist/memory/memory-log.entity.d.ts +152 -14
  46. package/dist/memory/memory-log.entity.functions.d.ts +35 -5
  47. package/dist/memory/memory-log.entity.functions.js +59 -20
  48. package/dist/memory/memory-log.entity.js +19 -6
  49. package/dist/memory/memory-session.js +10 -6
  50. package/dist/memory/memory.conformance.d.ts +8 -1
  51. package/dist/memory/memory.conformance.js +32 -0
  52. package/dist/models/auth-store.conformance.d.ts +13 -0
  53. package/dist/models/auth-store.conformance.js +73 -0
  54. package/dist/models/auth-store.conformance.test.d.ts +1 -0
  55. package/dist/models/model-catalog.conformance.d.ts +15 -0
  56. package/dist/models/model-catalog.conformance.js +27 -0
  57. package/dist/models/model-catalog.conformance.test.d.ts +1 -0
  58. package/dist/models/settings-store.conformance.d.ts +26 -0
  59. package/dist/models/settings-store.conformance.js +80 -0
  60. package/dist/models/settings-store.conformance.test.d.ts +1 -0
  61. package/dist/ports/activity.port.d.ts +35 -0
  62. package/dist/ports/activity.port.js +7 -0
  63. package/dist/ports/auth-store.port.d.ts +7 -5
  64. package/dist/ports/bash.port.d.ts +22 -0
  65. package/dist/ports/bash.port.js +4 -0
  66. package/dist/ports/capability.port.d.ts +11 -1
  67. package/dist/ports/file-system.port.d.ts +44 -15
  68. package/dist/ports/file-system.port.js +1 -7
  69. package/dist/ports/memory.port.d.ts +13 -1
  70. package/dist/ports/model-catalog.port.d.ts +4 -1
  71. package/dist/ports/run-context.port.d.ts +6 -1
  72. package/dist/ports/session-log.port.d.ts +4 -1
  73. package/dist/ports/sessions.port.d.ts +35 -6
  74. package/dist/ports/settings-store.port.d.ts +11 -7
  75. package/dist/ports/step-loop.port.d.ts +30 -2
  76. package/dist/ports/tasks.port.d.ts +5 -1
  77. package/dist/ports/turn-admission.port.d.ts +8 -2
  78. package/dist/ports/turn-events.port.d.ts +8 -1
  79. package/dist/ports/turn-scope.port.d.ts +19 -1
  80. package/dist/ports/turn.port.d.ts +70 -5
  81. package/dist/ports/utility-llm.port.d.ts +2 -0
  82. package/dist/session/activity.entity.d.ts +42 -0
  83. package/dist/session/activity.entity.functions.d.ts +21 -0
  84. package/dist/session/activity.entity.functions.js +23 -0
  85. package/dist/session/activity.entity.functions.test.d.ts +1 -0
  86. package/dist/session/activity.entity.js +15 -0
  87. package/dist/session/chassis.js +2 -1
  88. package/dist/session/session-event.entity.d.ts +68 -6
  89. package/dist/session/session-event.entity.functions.d.ts +34 -2
  90. package/dist/session/session-event.entity.functions.js +54 -16
  91. package/dist/session/session-event.entity.js +19 -2
  92. package/dist/session/session-log.conformance.d.ts +4 -3
  93. package/dist/session/session-log.conformance.js +16 -14
  94. package/dist/session/session-log.recovery.conformance.d.ts +40 -0
  95. package/dist/session/session-log.recovery.conformance.js +143 -0
  96. package/dist/session/session-log.recovery.conformance.test.d.ts +1 -0
  97. package/dist/session/sessions.conformance.d.ts +79 -0
  98. package/dist/session/sessions.conformance.js +355 -0
  99. package/dist/session/sessions.entity.d.ts +36 -3
  100. package/dist/session/sessions.entity.functions.d.ts +42 -0
  101. package/dist/session/sessions.entity.functions.js +26 -0
  102. package/dist/session/sessions.entity.js +17 -4
  103. package/dist/session/sessions.entity.test.d.ts +1 -0
  104. package/dist/session/tasks.conformance.d.ts +70 -0
  105. package/dist/session/tasks.conformance.js +213 -0
  106. package/dist/session/tasks.conformance.test.d.ts +1 -0
  107. package/dist/turn/model-request.entity.d.ts +4 -0
  108. package/dist/turn/model-request.entity.js +7 -2
  109. package/dist/turn/step-loop.conformance.d.ts +4 -1
  110. package/dist/turn/step-loop.conformance.js +73 -7
  111. package/dist/turn/tool-registry.conformance.d.ts +38 -0
  112. package/dist/turn/tool-registry.conformance.js +206 -0
  113. package/dist/turn/turn-bus.js +4 -3
  114. package/dist/turn/turn-event.entity.d.ts +134 -34
  115. package/dist/turn/turn-event.entity.functions.d.ts +11 -0
  116. package/dist/turn/turn-event.entity.functions.js +9 -1
  117. package/dist/turn/turn-event.entity.js +76 -3
  118. package/dist/turn/turn-event.entity.test.d.ts +1 -0
  119. package/dist/turn/turn-lifecycle.d.ts +46 -8
  120. package/dist/turn/turn-lifecycle.js +33 -3
  121. package/dist/turn/turn-lifecycle.test.d.ts +1 -0
  122. package/dist/turn/turn-run.d.ts +15 -3
  123. package/dist/turn/turn-run.js +37 -9
  124. package/dist/turn/turn.adapter.d.ts +6 -5
  125. package/dist/turn/turn.adapter.js +15 -3
  126. package/dist/turn/turn.conformance.d.ts +11 -2
  127. package/dist/turn/turn.conformance.js +63 -2
  128. package/dist/turn/user-message.entity.d.ts +14 -4
  129. package/dist/turn/user-message.entity.functions.d.ts +21 -0
  130. package/dist/turn/user-message.entity.functions.js +24 -0
  131. package/dist/turn/user-message.entity.js +6 -8
  132. package/dist/turn/user-message.entity.test.d.ts +1 -0
  133. package/dist/util/glob.d.ts +30 -0
  134. package/dist/util/glob.js +91 -0
  135. package/dist/util/glob.test.d.ts +1 -0
  136. package/dist/workspace/bash.conformance.d.ts +37 -0
  137. package/dist/workspace/bash.conformance.js +120 -0
  138. package/dist/workspace/bash.conformance.test.d.ts +1 -0
  139. package/dist/workspace/file-system.conformance.d.ts +38 -0
  140. package/dist/workspace/file-system.conformance.js +254 -0
  141. package/dist/workspace/file-system.conformance.test.d.ts +1 -0
  142. package/dist/workspace/file-system.memory.adapter.d.ts +23 -0
  143. package/dist/workspace/file-system.memory.adapter.js +132 -0
  144. package/package.json +1 -1
  145. package/dist/harness/skill-files.d.ts +0 -17
  146. package/dist/harness/skill-files.js +0 -25
  147. package/dist/ports/shell.port.d.ts +0 -32
  148. package/dist/ports/shell.port.js +0 -16
@@ -0,0 +1,7 @@
1
+ import { Context } from "effect";
2
+
3
+ export class TurnActivity extends Context.Service()("efferent/TurnActivity") {
4
+ }
5
+
6
+ export class TaskActivity extends Context.Service()("efferent/TaskActivity") {
7
+ }
@@ -32,16 +32,18 @@ declare const AuthStore_base: Context.ServiceClass<AuthStore, "@xandreed/core/Au
32
32
  readonly all: Effect.Effect<ReadonlyMap<string, Credential>, AuthError>;
33
33
  readonly get: (p: ProviderId) => Effect.Effect<Option.Option<Credential>, AuthError>;
34
34
  readonly resolveKey: (p: ProviderId) => Effect.Effect<Option.Option<Redacted.Redacted<string>>, AuthError>;
35
- /** Persist a credential (the human's `:login` action). */
35
+ /** Persist a credential (a login). */
36
36
  readonly set: (p: ProviderId, credential: Credential) => Effect.Effect<void, AuthError>;
37
- /** Remove a provider's credential everywhere it is stored (`:logout`). */
37
+ /** Remove a provider's credential everywhere it is stored (a logout). */
38
38
  readonly remove: (p: ProviderId) => Effect.Effect<void, AuthError>;
39
39
  }>;
40
40
  /**
41
41
  * The credential store, read lazily per request: `resolveKey` returns a
42
- * usable secret (refreshing + persisting a near-expiry OAuth token first),
43
- * so a credential added mid-session takes effect on the next call with no
44
- * restart. `None` means the provider is not configured.
42
+ * usable secret (refreshing a near-expiry OAuth token first, and persisting
43
+ * it where the store keeps credentials), so a credential added mid-session
44
+ * takes effect on the next call with no restart. `None` means the provider
45
+ * is not configured. `authStoreConformance` checks a store against this
46
+ * contract.
45
47
  */
46
48
  export declare class AuthStore extends AuthStore_base {
47
49
  }
@@ -0,0 +1,22 @@
1
+ import { Context } from "effect";
2
+ import type { Effect, Stream } from "effect";
3
+ import type { BashError, BashEvent, BashOptions, BashResult } from "../domain/bash.entity.js";
4
+ declare const Bash_base: Context.ServiceClass<Bash, "@xandreed/core/Bash", {
5
+ /** Output chunks as they arrive, then exactly one `BashExit`. */
6
+ readonly stream: (command: string, options: BashOptions) => Stream.Stream<BashEvent, BashError>;
7
+ /** The whole output and how the command ended (`collectBashResult` over `stream`). */
8
+ readonly exec: (command: string, options: BashOptions) => Effect.Effect<BashResult, BashError>;
9
+ }>;
10
+ /**
11
+ * Commands run with `bash -c`, each in a process group of its own. A
12
+ * non-zero exit, a signal and a timeout are results the caller reads,
13
+ * never errors: `BashError` means the command could not run at all. When a
14
+ * command times out or its caller stops waiting (the stream or the `exec`
15
+ * is interrupted), the adapter kills its whole process group, so what it
16
+ * started in the background goes with it; an adapter may also do that when
17
+ * the command exits. The environment is built with `resolveBashEnv`.
18
+ * `bashConformance` is the contract as runnable checks.
19
+ */
20
+ export declare class Bash extends Bash_base {
21
+ }
22
+ export {};
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+
3
+ export class Bash extends Context.Service()("@xandreed/core/Bash") {
4
+ }
@@ -1,6 +1,7 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect, Option } from "effect";
3
3
  import type { LanguageModel, Tool } from "effect/ai";
4
+ import type { Media } from "../domain/media.entity.js";
4
5
  import type { AgentMessage } from "../domain/message.entity.js";
5
6
  import type { TokenUsage } from "../domain/token-usage.entity.js";
6
7
  import type { PromptTier, SkillDefinition, ToolAnnotations } from "../harness/capability.entity.js";
@@ -38,9 +39,16 @@ export interface ToolViewDefinition<Result, Params> {
38
39
  /** The older-turn form; None keeps `render`'s text. */
39
40
  readonly compact: Option.Option<(result: Result, params: Params) => string>;
40
41
  readonly subjects: (result: Result, params: Params) => ReadonlyArray<Subject>;
41
- /** Files and images the result carries, kept by reference. */
42
+ /** Files and images the result points to by URL, kept by reference (shown as reference lines). */
42
43
  readonly artifacts: (result: Result, params: Params) => ReadonlyArray<ArtifactRef>;
43
44
  readonly digest: Option.Option<DigestDefinition<Result, Params>>;
45
+ /**
46
+ * The images the model is sent with the result: `Media` values the result
47
+ * holds (one it does not hold is ignored). None: every image it holds.
48
+ * A tool returns images as `Media` values in its success value;
49
+ * `Tool.toModelOutput` is not used.
50
+ */
51
+ readonly media: Option.Option<(result: Result, params: Params) => ReadonlyArray<Media>>;
44
52
  }
45
53
  /** The view as an author writes it; optional parts default in `defineTool`. */
46
54
  export interface ToolViewInput<Result, Params> {
@@ -50,6 +58,8 @@ export interface ToolViewInput<Result, Params> {
50
58
  readonly subjects?: (result: Result, params: Params) => ReadonlyArray<Subject>;
51
59
  readonly artifacts?: (result: Result, params: Params) => ReadonlyArray<ArtifactRef>;
52
60
  readonly digest?: DigestDefinition<Result, Params>;
61
+ /** The images among the result's the model is sent with it; omitted: every image the result holds. */
62
+ readonly media?: (result: Result, params: Params) => ReadonlyArray<Media>;
53
63
  }
54
64
  /** A typed tool definition as its author writes it (see `defineTool`). */
55
65
  export interface ToolDefinition<T extends Tool.Any, R> {
@@ -1,24 +1,53 @@
1
- import { Context, Schema } from "effect";
1
+ import { Context } from "effect";
2
2
  import type { Effect } from "effect";
3
- declare const FsError_base: Schema.Class<FsError, Schema.TaggedStruct<"FsError", {
4
- readonly path: Schema.String;
5
- readonly message: Schema.String;
6
- }>, import("effect/Cause").YieldableError>;
7
- export declare class FsError extends FsError_base {
8
- }
3
+ import type { DirEntry, FileInfo, FsError, GlobOptions, GlobResult, ReadRange, RecursiveOptions, StatOptions, WriteOptions } from "../domain/file-system.entity.js";
9
4
  declare const FileSystem_base: Context.ServiceClass<FileSystem, "@xandreed/core/FileSystem", {
5
+ /** A file's text, decoded as UTF-8. */
10
6
  readonly read: (path: string) => Effect.Effect<string, FsError>;
11
- readonly write: (path: string, content: string) => Effect.Effect<void, FsError>;
7
+ /** A file's bytes, or a range of them; a range past the end returns what there is. */
8
+ readonly readBytes: (path: string, range?: ReadRange) => Effect.Effect<Uint8Array, FsError>;
9
+ /** Create or replace a file (text is written as UTF-8). Without `createParents`, a missing parent fails with `NotFound`. */
10
+ readonly write: (path: string, content: string | Uint8Array, options?: WriteOptions) => Effect.Effect<void, FsError>;
11
+ /** Add to the end of a file, creating it when missing. */
12
+ readonly append: (path: string, content: string | Uint8Array, options?: WriteOptions) => Effect.Effect<void, FsError>;
13
+ /**
14
+ * Move a file, directory or link (the link itself, not its target); an
15
+ * existing file at `to` is replaced. A failure names the side that caused
16
+ * it: `from` when it is missing, `to` for a missing parent there or what
17
+ * is in the way (`IsDirectory`, `NotDirectory`, `NotEmpty`).
18
+ */
19
+ readonly rename: (from: string, to: string) => Effect.Effect<void, FsError>;
20
+ readonly stat: (path: string, options?: StatOptions) => Effect.Effect<FileInfo, FsError>;
21
+ /** Whether the path exists (a link whose target is missing does not). A missing path is `false`, not an error. */
12
22
  readonly exists: (path: string) => Effect.Effect<boolean, FsError>;
13
- readonly list: (dir: string) => Effect.Effect<ReadonlyArray<string>, FsError>;
14
- readonly mkdir: (dir: string) => Effect.Effect<void, FsError>;
15
- /** Resolve symlinks to the canonical absolute path. */
23
+ /** A directory's entries, sorted by name (code-unit order), links not followed. */
24
+ readonly list: (dir: string) => Effect.Effect<ReadonlyArray<DirEntry>, FsError>;
25
+ /** The paths under `options.cwd` the pattern matches (`*`, `**`, `?`, `[...]`, `{a,b}`); links are never entered. */
26
+ readonly glob: (pattern: string, options: GlobOptions) => Effect.Effect<GlobResult, FsError>;
27
+ /** Create a directory. Without `recursive`, an existing path fails with `Exists` and a missing parent with `NotFound`; with it, existing directories are fine. */
28
+ readonly mkdir: (dir: string, options?: RecursiveOptions) => Effect.Effect<void, FsError>;
29
+ /** The canonical absolute path, every symbolic link resolved. */
16
30
  readonly realPath: (path: string) => Effect.Effect<string, FsError>;
17
- /** Delete a file. A missing path is a no-op (idempotent), not an error. */
18
- readonly remove: (path: string) => Effect.Effect<void, FsError>;
31
+ /**
32
+ * Delete a file, a link (not its target) or a directory. A missing path
33
+ * is a no-op. Without `recursive`, an empty directory is removed and one
34
+ * with entries fails with `NotEmpty`. Links in the parent are followed
35
+ * and the last name never is, trailing slash or not (`link/` removes
36
+ * the link). The root, and a path whose last segment is `.` or `..`,
37
+ * fail with `Invalid`.
38
+ */
39
+ readonly remove: (path: string, options?: RecursiveOptions) => Effect.Effect<void, FsError>;
19
40
  }>;
20
- /** Minimal filesystem port for agent tools. Paths are absolute or
21
- * caller-resolved — the port does no cwd magic. */
41
+ /**
42
+ * The file system agent tools work through. Paths are absolute (the port
43
+ * does no cwd magic) and POSIX; every failure is an `FsError` whose
44
+ * `reason` is the same on every backend. Calls follow symbolic links,
45
+ * except `list` (which reports links as `symlink`), `remove` (which removes
46
+ * the link), `rename` (which moves the link, not its target) and `stat`
47
+ * with `followSymlinks: false`. `fileSystemConformance`
48
+ * is the contract as runnable checks; `FileSystemMemoryLive` is the
49
+ * in-memory backend.
50
+ */
22
51
  export declare class FileSystem extends FileSystem_base {
23
52
  }
24
53
  export {};
@@ -1,10 +1,4 @@
1
- import { Context, Schema } from "effect";
2
-
3
- export class FsError extends Schema.TaggedError()("FsError", {
4
- path: Schema.String,
5
- message: Schema.String
6
- }) {
7
- }
1
+ import { Context } from "effect";
8
2
 
9
3
  export class FileSystem extends Context.Service()("@xandreed/core/FileSystem") {
10
4
  }
@@ -1,5 +1,6 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect, Option, Scope } from "effect";
3
+ import type { MediaSlot } from "../domain/media.entity.js";
3
4
  import type { AgentMessage, ConversationId } from "../domain/message.entity.js";
4
5
  import type { TokenUsage } from "../domain/token-usage.entity.js";
5
6
  import type { HarnessError } from "../harness/plugin.entity.js";
@@ -19,6 +20,13 @@ export interface ToolView {
19
20
  readonly subjects: ReadonlyArray<Subject>;
20
21
  readonly artifacts: ReadonlyArray<ArtifactRef>;
21
22
  readonly pinned: boolean;
23
+ /**
24
+ * The images of the encoded result the model is sent with it, where they
25
+ * are in it (`mediaSlotsOf`). Memory checks each one when it records the
26
+ * result (`toolResultMedia`): an image that fails, or one over
27
+ * `MEDIA_LIMITS.toolResultImages`, is a label line in the stored view.
28
+ */
29
+ readonly media?: ReadonlyArray<MediaSlot>;
22
30
  }
23
31
  /** What the digester returns: the item keys to keep, or a summary. */
24
32
  export interface DigestOutcome {
@@ -72,7 +80,11 @@ export interface MemoryReader {
72
80
  export interface MaintainSignal {
73
81
  readonly phase: "turn-start" | "step";
74
82
  readonly lastUsage: Option.Option<TokenUsage>;
75
- /** Tokens available to the messages (system and tool schemas already deducted). */
83
+ /**
84
+ * Tokens available to the messages, at least 1: at `step`, what the system
85
+ * prompt and the active tool schemas leave of the run's budget; at
86
+ * `turn-start`, the run's whole budget (nothing is built yet).
87
+ */
76
88
  readonly budgetTokens: number;
77
89
  readonly views: ToolViews;
78
90
  }
@@ -4,7 +4,10 @@ import type { ModelCatalogEntry } from "../domain/model-catalog.entity.js";
4
4
  declare const ModelCatalog_base: Context.ServiceClass<ModelCatalog, "@xandreed/core/ModelCatalog", {
5
5
  readonly list: Effect.Effect<ReadonlyArray<ModelCatalogEntry>>;
6
6
  }>;
7
- /** Provider-neutral discovery of models usable by configured adapters. */
7
+ /**
8
+ * Provider-neutral discovery of models usable by configured adapters.
9
+ * `modelCatalogConformance` checks a catalog against this contract.
10
+ */
8
11
  export declare class ModelCatalog extends ModelCatalog_base {
9
12
  }
10
13
  export {};
@@ -1,8 +1,9 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect } from "effect";
3
3
  import type { ConversationId } from "../domain/message.entity.js";
4
+ import type { TokenUsage } from "../domain/token-usage.entity.js";
4
5
  import type { HarnessError } from "../harness/plugin.entity.js";
5
- import type { SessionAddress } from "../session/sessions.entity.js";
6
+ import type { AdmittedTurn, SessionAddress } from "../session/sessions.entity.js";
6
7
  import type { UserMessage } from "../turn/user-message.entity.js";
7
8
  import type { MemoryReader } from "./memory.port.js";
8
9
  import type { TurnEventsService, TurnTasksService } from "./turn-events.port.js";
@@ -12,6 +13,8 @@ declare const RunContext_base: Context.ServiceClass<RunContext, "efferent/RunCon
12
13
  readonly session: SessionAddress;
13
14
  readonly runId: string;
14
15
  readonly userMessage: UserMessage;
16
+ /** The admitted turn as the session began it: its number, key, origin, command, claimed inbox items and the host's `meta`. */
17
+ readonly admitted: AdmittedTurn;
15
18
  readonly memory: MemoryReader;
16
19
  readonly events: TurnEventsService;
17
20
  readonly tasks: TurnTasksService;
@@ -21,6 +24,8 @@ declare const RunContext_base: Context.ServiceClass<RunContext, "efferent/RunCon
21
24
  readonly flush: Effect.Effect<void, HarnessError>;
22
25
  /** Run a host store write in journal order (after everything queued before it). */
23
26
  readonly write: <A, E>(op: Effect.Effect<A, E>) => Effect.Effect<A, E | HarnessError>;
27
+ /** The turn's usage so far: the usage of every `assistant.message` published in the turn, summed. */
28
+ readonly usage: Effect.Effect<TokenUsage>;
24
29
  }>;
25
30
  /** The current run, as tools, sections and run layers see it. Provided by the turn. */
26
31
  export declare class RunContext extends RunContext_base {
@@ -18,7 +18,10 @@ declare const SessionLog_base: Context.ServiceClass<SessionLog, "efferent/Sessio
18
18
  * session plus its head (see `session-log.entity.ts`). Everything above it
19
19
  * (turns, leases, the inbox, memory) is the sessions plugin's; a backend
20
20
  * only keeps the log, the head and the compare-and-swap, and passes
21
- * `sessionLogConformance`.
21
+ * `sessionLogConformance`. One event can be large: a user's images are
22
+ * stored inline in its `turn.started`, up to `MEDIA_LIMITS.requestChars`
23
+ * base64 characters together, so a backend must hold an event of that size
24
+ * (the kit checks one).
22
25
  */
23
26
  export declare class SessionLog extends SessionLog_base {
24
27
  }
@@ -1,9 +1,10 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect, Option, Scope, Stream } from "effect";
3
+ import type { AttachmentRejected } from "../domain/media.entity.js";
3
4
  import type { ConversationId } from "../domain/message.entity.js";
4
5
  import type { HarnessError } from "../harness/plugin.entity.js";
5
6
  import type { JsonObject, SessionExists, SessionListCursor, SessionLogError, SessionLogEvent, SessionMissing } from "../session/session-log.entity.js";
6
- import type { AdmittedTurn, BeginTurn, InboxFull, InboxItem, KeyConflict, NothingPending, ReservedKind, SessionAddress, SessionBusy, SessionPage, SessionView, TurnClosed, TurnDuplicate, TurnEnding, TurnRefused } from "../session/sessions.entity.js";
7
+ import type { AdmittedTurn, BeginTurn, InboxFull, InboxItem, KeyConflict, NothingPending, NotResumable, ReservedKind, SessionAddress, SessionBusy, SessionPage, SessionView, TurnClosed, TurnDuplicate, TurnEnding, TurnRefused } from "../session/sessions.entity.js";
7
8
  /** One event to record; the writer stamps the turn (or none, outside a turn). */
8
9
  export interface TurnDraft {
9
10
  readonly kind: string;
@@ -60,8 +61,9 @@ export interface TurnWriter {
60
61
  * refused, and `flush` still waits for what is queued. Calls while it runs
61
62
  * share its result. A stored end, or a turn closed elsewhere, is final;
62
63
  * after any other failure (a store error) writes are admitted again and a
63
- * later end, the scope's own at the latest, tries again. Returns how many
64
- * inbox items wait.
64
+ * later end, the scope's own at the latest, tries again: the scope's with
65
+ * the last ending whose commit was tried. Returns how many inbox items
66
+ * wait.
65
67
  */
66
68
  readonly end: (ending: TurnEnding) => Effect.Effect<{
67
69
  readonly pending: number;
@@ -113,17 +115,44 @@ declare const Sessions_base: Context.ServiceClass<Sessions, "efferent/Sessions",
113
115
  }, SessionMissing | SessionLogError>;
114
116
  /**
115
117
  * Open a turn, through the host's TurnAdmission. Its writer lives in the
116
- * scope; a writer the scope closes unended ends as failed (or interrupted).
118
+ * scope; a writer the scope closes unended ends with the last ending it
119
+ * tried to store, or, when none was tried, as failed (`turn.unended`), or
120
+ * as interrupted (`turn.interrupted`) when the scope was interrupted.
121
+ * The input's `meta` is stored in `turn.started` and shown on the writer's
122
+ * `admitted`; it is not part of the key's fingerprint.
123
+ * A user's turn with `resumes: n` resumes turn `n`: it must be the
124
+ * session's last turn begun by a user (only inbox turns, and resumes of it
125
+ * that failed, were cancelled or were interrupted, may have run since) and
126
+ * have ended cancelled or interrupted (reaped included), or the begin
127
+ * fails with `NotResumable` and opens nothing. The check runs after the
128
+ * key's (a retried resume is `TurnDuplicate`) and after `SessionBusy`, in
129
+ * the opening commit; an `n` that is not a whole number of at least 1 is
130
+ * `NotResumable` (`missing`) before anything else, the admission included.
131
+ * `resumes` is stored in `turn.started`, shown on `admitted` and part of
132
+ * the key's fingerprint.
133
+ * A user message is normalised first (`normalizeUserMessage`), before
134
+ * anything else: an image its bytes do not support fails with
135
+ * `AttachmentRejected`, and the normalised message is what is stored,
136
+ * admitted and compared with an earlier turn of the same key.
117
137
  */
118
- readonly begin: (address: SessionAddress, input: BeginTurn) => Effect.Effect<TurnWriter, SessionBusy | TurnDuplicate | KeyConflict | NothingPending | TurnRefused | SessionMissing | SessionLogError, Scope.Scope>;
138
+ readonly begin: (address: SessionAddress, input: BeginTurn) => Effect.Effect<TurnWriter, AttachmentRejected | SessionBusy | TurnDuplicate | KeyConflict | NothingPending | NotResumable | TurnRefused | SessionMissing | SessionLogError, Scope.Scope>;
119
139
  /** Put an item in the inbox (once per id); `pending` counts what waits after it. */
120
140
  readonly deliver: (address: SessionAddress, item: InboxItem) => Effect.Effect<{
121
141
  readonly delivered: boolean;
122
142
  readonly pending: number;
123
143
  }, InboxFull | SessionMissing | SessionLogError>;
124
- /** Run inbox turns while items wait, the session is free and the host admits them, at most `maxTurns` (3). */
144
+ /**
145
+ * Run inbox turns while items wait, the session is free and the host
146
+ * admits them, at most `maxTurns` (3), each begun with `meta`. A turn whose
147
+ * `run` succeeds ends completed; one whose `run` fails ends with its
148
+ * error's code (`turnEndingOf`, a `HarnessError` carried as a defect
149
+ * included): `turn.failed`, `turn.defect` or `turn.interrupted` when it has
150
+ * none. A `run` that interrupts itself counts an attempt for the items it
151
+ * took, even when it wrote nothing.
152
+ */
125
153
  readonly drain: <E, R>(address: SessionAddress, run: (writer: TurnWriter) => Effect.Effect<void, E, R>, options?: {
126
154
  readonly maxTurns?: number;
155
+ readonly meta?: JsonObject;
127
156
  }) => Effect.Effect<{
128
157
  readonly turns: number;
129
158
  }, SessionMissing | SessionLogError, R>;
@@ -28,19 +28,23 @@ export declare const SETTINGS_KEYS: readonly ["fallbackModel", "sandbox", "viMod
28
28
  export type SettingsKey = (typeof SETTINGS_KEYS)[number];
29
29
  declare const SettingsStore_base: Context.ServiceClass<SettingsStore, "@xandreed/core/SettingsStore", {
30
30
  readonly load: Effect.Effect<EngineSettings, SettingsError>;
31
- /** Persist one role's model selection (the human's `:model` action);
32
- * `None` clears the role so it falls back to its default again. */
31
+ /** Persist one role's model selection (a role choice); `None` clears
32
+ * the role so it falls back to its default again. A selection that is
33
+ * not `"<provider>:<modelId>"` fails with a `SettingsError` and writes
34
+ * nothing. */
33
35
  readonly setRole: (role: ModelRole, selection: Option.Option<string>) => Effect.Effect<void, SettingsError>;
34
- /** Persist one non-role key from its STRING form (the `:settings`
35
- * action): the adapter validates + coerces per key ("true", "3",
36
- * "provider:model") and rejects a value that doesn't parse; `None`
37
- * clears the key. */
36
+ /** Persist one non-role key from its STRING form (a settings edit): the
37
+ * adapter validates + coerces per key ("true", "3", "provider:model")
38
+ * and rejects a value that doesn't parse with a `SettingsError`,
39
+ * writing nothing; `None` clears the key. */
38
40
  readonly set: (key: SettingsKey, value: Option.Option<string>) => Effect.Effect<void, SettingsError>;
39
41
  }>;
40
42
  /**
41
43
  * Read-through settings port. `load` re-reads the backing store on every call
42
44
  * so a value changed mid-session takes effect on the next turn — callers must
43
- * not cache the result across turns.
45
+ * not cache the result across turns. A store may be read-only: then
46
+ * `setRole` and `set` fail with a `SettingsError` and write nothing.
47
+ * `settingsStoreConformance` checks a store against this contract.
44
48
  */
45
49
  export declare class SettingsStore extends SettingsStore_base {
46
50
  }
@@ -2,6 +2,7 @@ import { Context } from "effect";
2
2
  import type { Effect, Option } from "effect";
3
3
  import type { LanguageModel, Prompt, Tool } from "effect/ai";
4
4
  import type { AgentMessage } from "../domain/message.entity.js";
5
+ import type { TokenUsage } from "../domain/token-usage.entity.js";
5
6
  import type { HarnessError } from "../harness/plugin.entity.js";
6
7
  import type { LogEntry } from "../memory/memory-log.entity.js";
7
8
  import type { CompletionVerdict } from "../turn/turn-event.entity.js";
@@ -44,12 +45,31 @@ export interface StepRequest {
44
45
  readonly dispatch: (step: number, request: ModelDispatch) => Effect.Effect<void, HarnessError>;
45
46
  /** Persist a step's appended messages; returns their log entries. */
46
47
  readonly record: (step: number, tail: ReadonlyArray<AgentMessage>) => Effect.Effect<ReadonlyArray<LogEntry>, HarnessError>;
48
+ /**
49
+ * A provider refused the step's request over an image (an `InvalidRequestError`
50
+ * that names one; `reason` is what it said). Records an `OmitMedia`
51
+ * compaction of the entries whose images that request sent and answers
52
+ * true, so the loop plans the step again and sends it once more, the
53
+ * images as label lines; false when the request sent no image, and the
54
+ * step fails. The step's next `plan`, that one only, keeps the model
55
+ * choice, system prompt and directive the refused request was planned
56
+ * with; any other `plan` of a step asks the policy again.
57
+ */
58
+ readonly onMediaRejected: (reason: string) => Effect.Effect<boolean, HarnessError>;
47
59
  readonly completion: (step: StepInfo) => Effect.Effect<CompletionVerdict, HarnessError>;
48
60
  readonly steering: Effect.Effect<Option.Option<string>, HarnessError>;
49
61
  readonly correctives: Option.Option<Correctives>;
50
62
  readonly events: TurnEventsService;
51
63
  readonly tasks: TurnTasksService;
52
64
  readonly cacheKey: Option.Option<string>;
65
+ /**
66
+ * Whether the run's spans may carry prompt and response content: the loop
67
+ * must provide it as `CurrentTraceContent` to the model calls it makes,
68
+ * whatever the caller's (`stepLoopConformance` checks it), so they read it
69
+ * too. The policy's `captureTraceContent`, else the agent's, else
70
+ * `CurrentTraceContent` where the run starts (off by default).
71
+ */
72
+ readonly captureTraceContent: boolean;
53
73
  }
54
74
  export interface ModelDispatch {
55
75
  readonly prompt: Prompt.Prompt;
@@ -62,6 +82,8 @@ export interface RunResult {
62
82
  readonly reason: "completed" | "step-cap" | "degenerate-loop";
63
83
  readonly text: string;
64
84
  readonly steps: number;
85
+ /** The run's provider calls summed: exactly the usage its `assistant.message` events carried. */
86
+ readonly usage: TokenUsage;
65
87
  }
66
88
  declare const StepLoop_base: Context.ServiceClass<StepLoop, "efferent/StepLoop", {
67
89
  readonly id: string;
@@ -75,10 +97,16 @@ declare const StepLoop_base: Context.ServiceClass<StepLoop, "efferent/StepLoop",
75
97
  * (after the tail is recorded) < `completion.evaluated`; a planned batch
76
98
  * makes no provider call; a forced tool choice is honoured; a verdict
77
99
  * awaiting tasks joins them and is evaluated once more; no provider call
78
- * follows a complete verdict.
100
+ * follows a complete verdict; `RunResult.usage` is the sum of the usage
101
+ * the run's `assistant.message` events carried; the model calls read the
102
+ * request's `captureTraceContent` as `CurrentTraceContent`. A loop that streams and
103
+ * falls back to a non-streamed call publishes `stream.fallback`; the kit
104
+ * does not stream, so it does not check that.
79
105
  * Every real provider attempt runs `request.dispatch` immediately before
80
106
  * invoking the model; a failed durable check prevents the call. Planned
81
- * batches skip that hook because they make no provider request.
107
+ * batches skip that hook because they make no provider request. A request
108
+ * a provider refuses over an image is sent once more after
109
+ * `request.onMediaRejected` answers true, planned again first.
82
110
  */
83
111
  export declare class StepLoop extends StepLoop_base {
84
112
  }
@@ -43,7 +43,11 @@ declare const TaskRunner_base: Context.ServiceClass<TaskRunner, "efferent/TaskRu
43
43
  * Where background work runs: a fiber of this process, or the host's own
44
44
  * mechanism (e.g. a serverless waitUntil). `run` returns once the work is
45
45
  * handed over; the work is told when it must be done by (Clock time), if
46
- * the runner has a limit, so a task ends interrupted rather than lost.
46
+ * the runner has a limit, so a task ends interrupted rather than lost. The
47
+ * work goes on after its caller (the turn that started a task) ends, and
48
+ * runs without the caller's services; on fibers of this process, it is
49
+ * interrupted when the runner's scope closes. `taskRunnerConformance`
50
+ * checks a runner.
47
51
  */
48
52
  export declare class TaskRunner extends TaskRunner_base {
49
53
  }
@@ -1,5 +1,6 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect } from "effect";
3
+ import type { JsonObject } from "../session/session-log.entity.js";
3
4
  import type { SessionAddress, TurnRefused } from "../session/sessions.entity.js";
4
5
  /** The turn a host is asked to admit. */
5
6
  export interface TurnToAdmit {
@@ -7,6 +8,10 @@ export interface TurnToAdmit {
7
8
  readonly origin: "user" | "inbox";
8
9
  readonly runId: string;
9
10
  readonly key: string;
11
+ /** The host's metadata the turn is begun with (`{}` when none was given): a tenant or a quota key to count by. */
12
+ readonly meta: JsonObject;
13
+ /** The earlier turn a user's turn resumes (a whole number of at least 1), only when it resumes one. */
14
+ readonly resumes?: number;
10
15
  }
11
16
  declare const TurnAdmission_base: Context.ServiceClass<TurnAdmission, "efferent/TurnAdmission", {
12
17
  readonly admit: <A, E>(turn: TurnToAdmit, open: Effect.Effect<A, E>) => Effect.Effect<A, E | TurnRefused>;
@@ -15,8 +20,9 @@ declare const TurnAdmission_base: Context.ServiceClass<TurnAdmission, "efferent/
15
20
  * The host's say on every turn before it opens: a user's, the inbox's, a
16
21
  * background task's. `admit` runs around the one commit that opens the turn,
17
22
  * never around the turn's later writes, so a host can count and open in one
18
- * transaction; a duplicate or a busy session fails inside it and counts
19
- * nothing. A refusal opens nothing.
23
+ * transaction; a duplicate, a key conflict, a busy session or a turn that
24
+ * cannot be resumed (`NotResumable`) fails inside it and counts nothing. A
25
+ * refusal opens nothing.
20
26
  */
21
27
  export declare class TurnAdmission extends TurnAdmission_base {
22
28
  }
@@ -24,7 +24,14 @@ export interface SubscribeOptions {
24
24
  */
25
25
  export interface TurnEventsService {
26
26
  readonly publish: (event: TurnEvent) => Effect.Effect<void, HarnessError>;
27
- /** Active until the caller's scope closes. `select` narrows and decodes. */
27
+ /**
28
+ * Active until the caller's scope closes. `select` narrows and decodes.
29
+ * The handler runs with the services where it subscribed, without their
30
+ * trace state (`replayableContext`): an inline handler's spans nest under
31
+ * the publisher's span, a background handler's under where it subscribed
32
+ * (its fiber starts there), and both read the publisher's
33
+ * `CurrentTraceContent`.
34
+ */
28
35
  readonly subscribe: <E, R>(select: (event: TurnEvent) => Option.Option<E>, handle: (event: E) => Effect.Effect<void, HarnessError, R>, options?: SubscribeOptions) => Effect.Effect<void, never, R | Scope.Scope>;
29
36
  /** Wait until every background subscription has handled what was published to it. */
30
37
  readonly drain: Effect.Effect<void, HarnessError>;
@@ -91,11 +91,29 @@ export interface TurnLiveInput {
91
91
  /** The agent-level defaults of one run; a policy overrides limits and budget. */
92
92
  export interface TurnRunOptions {
93
93
  readonly limits?: Partial<LoopLimits>;
94
- /** Input tokens one request may use (system, tool schemas and messages); 64 000 by default. */
94
+ /**
95
+ * The run's token budget when the policy gives none; 64 000 by default. An
96
+ * integer of at least 1 (else the run fails with `turn.budget`): the target
97
+ * memory compaction keeps each request under (system prompt, tool schemas
98
+ * and messages, estimated), not a provider limit. The memory strategy is
99
+ * given the whole budget at the turn's start, and at each step what the
100
+ * system prompt and the active tools' schemas leave of it
101
+ * (`context.budget` when they leave nothing): the window strategy
102
+ * compacts, spills and drops older turns to fit the budget it is given
103
+ * (and fails with `context.budget` when the current turn alone does not),
104
+ * the summary strategy summarizes past its `triggerRatio` of it, and the
105
+ * digester ignores it. Each step's `context.built` records it.
106
+ */
95
107
  readonly budgetTokens?: number;
96
108
  /** The provider prompt-cache key (see `cacheKeyOf`); none by default. */
97
109
  readonly cacheKey?: Option.Option<string>;
98
110
  /** A mid-run user message, consulted between steps. */
99
111
  readonly steering?: Effect.Effect<Option.Option<string>, HarnessError>;
112
+ /**
113
+ * Whether the run's spans may carry prompt and response content
114
+ * (`StepRequest.captureTraceContent`) when the policy does not say; when
115
+ * neither does, `CurrentTraceContent` where the run starts (off by default).
116
+ */
117
+ readonly captureTraceContent?: boolean;
100
118
  }
101
119
  export {};