@xandreed/core 0.12.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 (155) 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 -10
  37. package/dist/index.js +67 -14
  38. package/dist/loop/cacheKey.d.ts +1 -1
  39. package/dist/loop/mapping.d.ts +13 -7
  40. package/dist/loop/mapping.js +30 -6
  41. package/dist/loop/stepContext.d.ts +8 -0
  42. package/dist/loop/stepContext.js +1 -1
  43. package/dist/loop/traceContent.d.ts +29 -0
  44. package/dist/loop/traceContent.js +4 -0
  45. package/dist/loop/traceContent.test.d.ts +1 -0
  46. package/dist/memory/memory-log.entity.d.ts +152 -14
  47. package/dist/memory/memory-log.entity.functions.d.ts +35 -5
  48. package/dist/memory/memory-log.entity.functions.js +59 -20
  49. package/dist/memory/memory-log.entity.js +19 -6
  50. package/dist/memory/memory-session.js +10 -6
  51. package/dist/memory/memory.conformance.d.ts +8 -1
  52. package/dist/memory/memory.conformance.js +32 -0
  53. package/dist/models/auth-store.conformance.d.ts +13 -0
  54. package/dist/models/auth-store.conformance.js +73 -0
  55. package/dist/models/auth-store.conformance.test.d.ts +1 -0
  56. package/dist/models/model-catalog.conformance.d.ts +15 -0
  57. package/dist/models/model-catalog.conformance.js +27 -0
  58. package/dist/models/model-catalog.conformance.test.d.ts +1 -0
  59. package/dist/models/settings-store.conformance.d.ts +26 -0
  60. package/dist/models/settings-store.conformance.js +80 -0
  61. package/dist/models/settings-store.conformance.test.d.ts +1 -0
  62. package/dist/ports/activity.port.d.ts +35 -0
  63. package/dist/ports/activity.port.js +7 -0
  64. package/dist/ports/auth-store.port.d.ts +7 -5
  65. package/dist/ports/bash.port.d.ts +22 -0
  66. package/dist/ports/bash.port.js +4 -0
  67. package/dist/ports/capability.port.d.ts +11 -1
  68. package/dist/ports/file-system.port.d.ts +44 -15
  69. package/dist/ports/file-system.port.js +1 -7
  70. package/dist/ports/harness.port.d.ts +1 -99
  71. package/dist/ports/harness.port.js +0 -24
  72. package/dist/ports/memory.port.d.ts +13 -1
  73. package/dist/ports/model-catalog.port.d.ts +4 -1
  74. package/dist/ports/run-context.port.d.ts +6 -1
  75. package/dist/ports/session-log.port.d.ts +4 -1
  76. package/dist/ports/sessions.port.d.ts +35 -6
  77. package/dist/ports/settings-store.port.d.ts +11 -7
  78. package/dist/ports/step-loop.port.d.ts +30 -2
  79. package/dist/ports/tasks.port.d.ts +5 -1
  80. package/dist/ports/turn-admission.port.d.ts +8 -2
  81. package/dist/ports/turn-events.port.d.ts +8 -1
  82. package/dist/ports/turn-scope.port.d.ts +19 -1
  83. package/dist/ports/turn.port.d.ts +70 -5
  84. package/dist/ports/utility-llm.port.d.ts +2 -0
  85. package/dist/session/activity.entity.d.ts +42 -0
  86. package/dist/session/activity.entity.functions.d.ts +21 -0
  87. package/dist/session/activity.entity.functions.js +23 -0
  88. package/dist/session/activity.entity.functions.test.d.ts +1 -0
  89. package/dist/session/activity.entity.js +15 -0
  90. package/dist/session/chassis.js +2 -1
  91. package/dist/session/session-event.entity.d.ts +68 -6
  92. package/dist/session/session-event.entity.functions.d.ts +34 -2
  93. package/dist/session/session-event.entity.functions.js +54 -16
  94. package/dist/session/session-event.entity.js +19 -2
  95. package/dist/session/session-log.conformance.d.ts +4 -3
  96. package/dist/session/session-log.conformance.js +16 -14
  97. package/dist/session/session-log.recovery.conformance.d.ts +40 -0
  98. package/dist/session/session-log.recovery.conformance.js +143 -0
  99. package/dist/session/session-log.recovery.conformance.test.d.ts +1 -0
  100. package/dist/session/sessions.conformance.d.ts +79 -0
  101. package/dist/session/sessions.conformance.js +355 -0
  102. package/dist/session/sessions.entity.d.ts +36 -3
  103. package/dist/session/sessions.entity.functions.d.ts +42 -0
  104. package/dist/session/sessions.entity.functions.js +26 -0
  105. package/dist/session/sessions.entity.js +17 -4
  106. package/dist/session/sessions.entity.test.d.ts +1 -0
  107. package/dist/session/tasks.conformance.d.ts +70 -0
  108. package/dist/session/tasks.conformance.js +213 -0
  109. package/dist/session/tasks.conformance.test.d.ts +1 -0
  110. package/dist/turn/model-request.entity.d.ts +4 -0
  111. package/dist/turn/model-request.entity.js +7 -2
  112. package/dist/turn/step-loop.conformance.d.ts +4 -1
  113. package/dist/turn/step-loop.conformance.js +73 -7
  114. package/dist/turn/tool-registry.conformance.d.ts +38 -0
  115. package/dist/turn/tool-registry.conformance.js +206 -0
  116. package/dist/turn/turn-bus.js +4 -3
  117. package/dist/turn/turn-event.entity.d.ts +134 -34
  118. package/dist/turn/turn-event.entity.functions.d.ts +11 -0
  119. package/dist/turn/turn-event.entity.functions.js +9 -1
  120. package/dist/turn/turn-event.entity.js +76 -3
  121. package/dist/turn/turn-event.entity.test.d.ts +1 -0
  122. package/dist/turn/turn-lifecycle.d.ts +46 -8
  123. package/dist/turn/turn-lifecycle.js +33 -3
  124. package/dist/turn/turn-lifecycle.test.d.ts +1 -0
  125. package/dist/turn/turn-run.d.ts +15 -3
  126. package/dist/turn/turn-run.js +37 -9
  127. package/dist/turn/turn.adapter.d.ts +6 -5
  128. package/dist/turn/turn.adapter.js +15 -3
  129. package/dist/turn/turn.conformance.d.ts +11 -2
  130. package/dist/turn/turn.conformance.js +63 -2
  131. package/dist/turn/user-message.entity.d.ts +14 -4
  132. package/dist/turn/user-message.entity.functions.d.ts +21 -0
  133. package/dist/turn/user-message.entity.functions.js +24 -0
  134. package/dist/turn/user-message.entity.js +6 -8
  135. package/dist/turn/user-message.entity.test.d.ts +1 -0
  136. package/dist/util/glob.d.ts +30 -0
  137. package/dist/util/glob.js +91 -0
  138. package/dist/util/glob.test.d.ts +1 -0
  139. package/dist/workspace/bash.conformance.d.ts +37 -0
  140. package/dist/workspace/bash.conformance.js +120 -0
  141. package/dist/workspace/bash.conformance.test.d.ts +1 -0
  142. package/dist/workspace/file-system.conformance.d.ts +38 -0
  143. package/dist/workspace/file-system.conformance.js +254 -0
  144. package/dist/workspace/file-system.conformance.test.d.ts +1 -0
  145. package/dist/workspace/file-system.memory.adapter.d.ts +23 -0
  146. package/dist/workspace/file-system.memory.adapter.js +132 -0
  147. package/package.json +6 -6
  148. package/dist/harness/session.entity.d.ts +0 -33
  149. package/dist/harness/session.entity.js +0 -25
  150. package/dist/harness/skill-files.d.ts +0 -17
  151. package/dist/harness/skill-files.js +0 -25
  152. package/dist/ports/conversation-store.port.d.ts +0 -127
  153. package/dist/ports/conversation-store.port.js +0 -36
  154. package/dist/ports/shell.port.d.ts +0 -32
  155. 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,112 +1,14 @@
1
1
  import { Context } from "effect";
2
- import type { Effect, Option, Stream } from "effect";
3
- import type { Tool, Toolkit } from "effect/ai";
4
- import type { AgentMessage, ConversationId } from "../domain/message.entity.js";
2
+ import type { Effect } from "effect";
5
3
  import type { HarnessError } from "../harness/plugin.entity.js";
6
- import type { EventBody, MemoryEntry, SessionEvent, SessionRecord } from "../harness/session.entity.js";
7
- import type { UserMessage } from "../turn/user-message.entity.js";
8
- declare const SessionStore_base: Context.ServiceClass<SessionStore, "efferent/SessionStore", {
9
- readonly create: (workspace: string, profile: string) => Effect.Effect<SessionRecord, HarnessError>;
10
- readonly get: (id: ConversationId) => Effect.Effect<SessionRecord, HarnessError>;
11
- readonly list: (workspace: string) => Effect.Effect<ReadonlyArray<SessionRecord>, HarnessError>;
12
- readonly read: (id: ConversationId, after: number) => Effect.Effect<ReadonlyArray<SessionEvent>, HarnessError>;
13
- readonly append: (id: ConversationId, body: EventBody) => Effect.Effect<SessionEvent, HarnessError>;
14
- readonly fork: (id: ConversationId, through: number) => Effect.Effect<SessionRecord, HarnessError>;
15
- }>;
16
- /** @deprecated Historical event vocabulary; live adapters project it over SessionLog. New hosts use Sessions and TurnWriter. */
17
- export declare class SessionStore extends SessionStore_base {
18
- }
19
- export interface LoopInput {
20
- readonly session: SessionRecord;
21
- readonly runId: string;
22
- readonly userMessage: UserMessage;
23
- readonly system: string;
24
- readonly publish: (event: EventBody) => Effect.Effect<SessionEvent, HarnessError>;
25
- readonly transient: (event: EventBody) => Effect.Effect<void>;
26
- readonly steering: Effect.Effect<Option.Option<string>, HarnessError>;
27
- /** Journal reads for memory plugins: events after `after`, filtered by name (all when empty). */
28
- readonly history: (after: number, names: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<SessionEvent>, HarnessError>;
29
- /** Host services the run's tools and hooks may require (data ports, per-turn models). */
30
- readonly services: Context.Context<never>;
31
- }
32
- declare const AgentLoop_base: Context.ServiceClass<AgentLoop, "efferent/AgentLoop", {
33
- readonly run: (input: LoopInput) => Effect.Effect<{
34
- readonly text: string;
35
- readonly outcome: "completed" | "partial";
36
- }, HarnessError>;
37
- }>;
38
- export declare class AgentLoop extends AgentLoop_base {
39
- }
40
- declare const DelegateLoop_base: Context.ServiceClass<DelegateLoop, "efferent/DelegateLoop", {
41
- readonly run: (input: LoopInput) => Effect.Effect<{
42
- readonly text: string;
43
- readonly outcome: "completed" | "partial";
44
- }, HarnessError>;
45
- }>;
46
- /** A workflow can delegate coding while retaining its own outer loop. */
47
- export declare class DelegateLoop extends DelegateLoop_base {
48
- }
49
- declare const AgentTools_base: Context.ServiceClass<AgentTools, "efferent/AgentTools", {
50
- readonly toolkit: Toolkit.Toolkit<Record<string, Tool.Any>>;
51
- readonly handlers: Context.Context<Tool.HandlersFor<Record<string, Tool.Any>>>;
52
- readonly prompt: string;
53
- }>;
54
- export declare class AgentTools extends AgentTools_base {
55
- }
56
4
  declare const ActionPolicy_base: Context.ServiceClass<ActionPolicy, "efferent/ActionPolicy", {
57
5
  readonly authorize: (tool: string, input: unknown) => Effect.Effect<void, HarnessError>;
58
6
  }>;
59
7
  export declare class ActionPolicy extends ActionPolicy_base {
60
8
  }
61
- declare const Approval_base: Context.ServiceClass<Approval, "efferent/Approval", {
62
- readonly request: (description: string) => Effect.Effect<boolean, HarnessError>;
63
- }>;
64
- export declare class Approval extends Approval_base {
65
- }
66
- declare const ContextManager_base: Context.ServiceClass<ContextManager, "efferent/ContextManager", {
67
- readonly compact: (messages: ReadonlyArray<AgentMessage>, tokens: number) => Effect.Effect<Option.Option<{
68
- readonly summary: string;
69
- readonly keepFrom: number;
70
- }>, HarnessError>;
71
- }>;
72
- export declare class ContextManager extends ContextManager_base {
73
- }
74
- declare const Memory_base: Context.ServiceClass<Memory, "efferent/Memory", {
75
- readonly recall: (workspace: string, query: string) => Effect.Effect<ReadonlyArray<MemoryEntry>, HarnessError>;
76
- readonly remember: (workspace: string, text: string) => Effect.Effect<MemoryEntry, HarnessError>;
77
- readonly forget: (workspace: string, id: string) => Effect.Effect<void, HarnessError>;
78
- }>;
79
- export declare class Memory extends Memory_base {
80
- }
81
9
  declare const SessionEnvironment_base: Context.ServiceClass<SessionEnvironment, "efferent/SessionEnvironment", {
82
10
  readonly workspace: string;
83
- readonly session?: SessionRecord;
84
11
  }>;
85
12
  export declare class SessionEnvironment extends SessionEnvironment_base {
86
13
  }
87
- declare const TurnHooks_base: Context.ServiceClass<TurnHooks, "efferent/TurnHooks", {
88
- readonly before: (input: LoopInput) => Effect.Effect<LoopInput, HarnessError>;
89
- readonly after: (input: LoopInput) => Effect.Effect<void, HarnessError>;
90
- }>;
91
- /** Ordered hooks alter a turn; observers subscribe to the journal separately. */
92
- export declare class TurnHooks extends TurnHooks_base {
93
- }
94
- export interface SessionHandle {
95
- readonly record: SessionRecord;
96
- readonly use: <I, A, B, E, R>(tag: Context.Service<I, A>, run: (service: A) => Effect.Effect<B, E, R>) => Effect.Effect<B, E | HarnessError, R>;
97
- readonly send: (text: string) => Effect.Effect<void, HarnessError>;
98
- readonly steer: (text: string) => Effect.Effect<void, HarnessError>;
99
- readonly interrupt: Effect.Effect<void>;
100
- readonly events: (after?: number) => Stream.Stream<SessionEvent, HarnessError>;
101
- readonly transient: Stream.Stream<EventBody>;
102
- readonly history: Effect.Effect<ReadonlyArray<SessionEvent>, HarnessError>;
103
- readonly busy: Effect.Effect<boolean>;
104
- readonly pending: Effect.Effect<ReadonlyArray<{
105
- readonly id: string;
106
- readonly text: string;
107
- }>>;
108
- readonly continue: Effect.Effect<void, HarnessError>;
109
- readonly refresh: Effect.Effect<void, HarnessError>;
110
- readonly close: Effect.Effect<void>;
111
- }
112
14
  export {};
@@ -1,31 +1,7 @@
1
1
  import { Context } from "effect";
2
2
 
3
- export class SessionStore extends Context.Service()("efferent/SessionStore") {
4
- }
5
-
6
- export class AgentLoop extends Context.Service()("efferent/AgentLoop") {
7
- }
8
-
9
- export class DelegateLoop extends Context.Service()("efferent/DelegateLoop") {
10
- }
11
-
12
- export class AgentTools extends Context.Service()("efferent/AgentTools") {
13
- }
14
-
15
3
  export class ActionPolicy extends Context.Service()("efferent/ActionPolicy") {
16
4
  }
17
5
 
18
- export class Approval extends Context.Service()("efferent/Approval") {
19
- }
20
-
21
- export class ContextManager extends Context.Service()("efferent/ContextManager") {
22
- }
23
-
24
- export class Memory extends Context.Service()("efferent/Memory") {
25
- }
26
-
27
6
  export class SessionEnvironment extends Context.Service()("efferent/SessionEnvironment") {
28
7
  }
29
-
30
- export class TurnHooks extends Context.Service()("efferent/TurnHooks") {
31
- }
@@ -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
  }