@tomradex/agent 0.16.3 → 0.17.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.
@@ -0,0 +1,303 @@
1
+ import type { Author } from "@tomradex/domain";
2
+ import type { RetryState } from "./turn.js";
3
+ import type { GeneralExecution } from "./workflow.js";
4
+ /**
5
+ * What the user was looking at when they sent a message. Kinds are open on
6
+ * purpose — a plugin can put its own surface on screen (a future "doc").
7
+ * Purely a hint for resolving "this/current"; tools still take explicit ids.
8
+ */
9
+ export interface FocusRef {
10
+ kind: string;
11
+ id: string;
12
+ /** Human-readable key where the entity has one (items do, boards don't). */
13
+ key?: string;
14
+ title: string;
15
+ }
16
+ /** Everything the panel knows how to draw. Engines emit these; the UI has no engine-specific code. */
17
+ export type Block = {
18
+ type: "text";
19
+ text: string;
20
+ focus?: FocusRef[];
21
+ attachments?: import("./media.js").MediaRef[]; /** Sent into a running turn; the id the panel gave it. */
22
+ steerId?: string;
23
+ } | {
24
+ type: "media";
25
+ media: import("./media.js").MediaRef;
26
+ caption?: string;
27
+ } | {
28
+ type: "trace";
29
+ tool: string;
30
+ args: unknown;
31
+ ok: boolean;
32
+ summary: string;
33
+ ms: number;
34
+ commandResult?: {
35
+ status: "success" | "no-match" | "condition-false" | "failed" | "declined" | "timed-out" | "interrupted";
36
+ exitCode: number | null;
37
+ output: string;
38
+ };
39
+ } | {
40
+ type: "question";
41
+ id: string;
42
+ text: string;
43
+ options?: string[];
44
+ multiple?: boolean;
45
+ freeText?: boolean;
46
+ } | {
47
+ type: "card";
48
+ id: string;
49
+ schema: string;
50
+ title: string;
51
+ data: unknown;
52
+ } | {
53
+ type: "status";
54
+ text: string;
55
+ } | {
56
+ type: "changes";
57
+ runId: string;
58
+ count: number;
59
+ items?: {
60
+ key: string;
61
+ label: string;
62
+ op: "created" | "updated" | "deleted";
63
+ }[];
64
+ } | {
65
+ type: "note";
66
+ text: string;
67
+ }
68
+ /** One stretch of failed model requests, stored once it ends: recovered, or the run gave up. */
69
+ | {
70
+ type: "retry";
71
+ attempts: number;
72
+ max?: number;
73
+ reason: string;
74
+ ms: number;
75
+ recovered: boolean;
76
+ }
77
+ /** `details`: what the engine knew beyond the one line, folded under it so it can be read and copied. */
78
+ | {
79
+ type: "error";
80
+ text: string;
81
+ details?: string;
82
+ };
83
+ export interface ThreadMessage {
84
+ id: string;
85
+ threadId: string;
86
+ seq: number;
87
+ role: "user" | "agent" | "system";
88
+ block: Block;
89
+ runId: string | null;
90
+ at: number;
91
+ }
92
+ /**
93
+ * Who owns a thread. `doctor`: a troubleshooting conversation, listed in the
94
+ * panel beside the assistant's. `probe`: a one-turn test the troubleshooter
95
+ * runs on an engine and deletes, never listed.
96
+ */
97
+ export type ThreadSurface = "assistant" | "code" | "doctor" | "probe";
98
+ /** The threads the assistant panel lists. */
99
+ export declare const PANEL_SURFACES: ThreadSurface[];
100
+ /** A `checklist` card: what was looked at, each row's state as it goes. */
101
+ export interface ChecklistCard {
102
+ rows: {
103
+ id: string;
104
+ title: string;
105
+ state: "pending" | "running" | "ok" | "warn" | "fail" | "skip";
106
+ line?: string;
107
+ }[];
108
+ }
109
+ /**
110
+ * What a person can do about a finding, drawn as a button. The backend words
111
+ * the label; the panel knows the verbs, so no plugin ships UI code for them.
112
+ */
113
+ export type CardAction =
114
+ /** Runs the engine's declared install or sign-in command in a terminal. */
115
+ {
116
+ kind: "install" | "sign-in";
117
+ engineId: string;
118
+ label: string;
119
+ }
120
+ /** Opens settings: the engine's own row, or model settings without one; `pane` opens that pane instead. */
121
+ | {
122
+ kind: "settings";
123
+ engineId?: string;
124
+ pane?: string;
125
+ label: string;
126
+ }
127
+ /** Moves that thread to another ready agent and sends its last message again. */
128
+ | {
129
+ kind: "retry-with";
130
+ threadId: string;
131
+ exclude?: string;
132
+ label: string;
133
+ }
134
+ /** Says this in the card's own thread. */
135
+ | {
136
+ kind: "say";
137
+ text: string;
138
+ label: string;
139
+ }
140
+ /** Installs one of Tomrade's own plugins from the official catalog; `done` is what the person is told once it is on. */
141
+ | {
142
+ kind: "install-plugin";
143
+ pluginId: string;
144
+ done: string;
145
+ label: string;
146
+ }
147
+ /** Opens the report about this thread, to read and send to Tomrade's developer. */
148
+ | {
149
+ kind: "report";
150
+ label: string;
151
+ }
152
+ /** Lets one of these agents look into it; `note` says what it will read and where that goes. */
153
+ | {
154
+ kind: "investigate";
155
+ engineIds: string[];
156
+ note: string;
157
+ label: string;
158
+ };
159
+ /** A `finding` card: one thing found, how sure, and what to do about it. */
160
+ export interface FindingCard {
161
+ tone: "ok" | "warn" | "fail";
162
+ headline: string;
163
+ body?: string;
164
+ confidence?: "high" | "medium" | "low";
165
+ actions: CardAction[];
166
+ }
167
+ export interface ThreadSummary {
168
+ execution?: GeneralExecution | null;
169
+ /** In a listing for the panel: a run of it is going right now. */
170
+ running?: boolean;
171
+ /** In a listing for the panel: it stopped on a usage limit that lifts then. */
172
+ parkedUntil?: number | null;
173
+ id: string;
174
+ projectId: string;
175
+ /** The workstream the thread stands on, and so the repository it runs in; null for a thread about the whole project. */
176
+ boardId: string | null;
177
+ /** The ticket the thread implements, when it was started from one. Such a thread runs a code engine in its own branch. */
178
+ itemId: string | null;
179
+ /** The branch the thread's worktree is on; null for a free-form thread, which sits detached at the default branch. */
180
+ branch: string | null;
181
+ title: string;
182
+ engine: string;
183
+ /** Which product surface owns the thread. Code sessions use Agent's worktree machinery without becoming Assistant conversations. */
184
+ surface: ThreadSurface;
185
+ permissionMode?: string | null;
186
+ /** Engine option overrides chosen for this thread (model, effort…); merged over the engine's settings. */
187
+ options?: Record<string, unknown> | null;
188
+ archived: boolean;
189
+ /** A person brought it back from the archive: it stays open until they put it away again. */
190
+ restored?: boolean;
191
+ /** What opened it, in the person's words — each named by the plugin that did; chips beside the title. */
192
+ origins?: string[];
193
+ createdAt: number;
194
+ updatedAt: number;
195
+ }
196
+ export type RunStatus = "running" | "done" | "asked" | "cancelled" | "error";
197
+ export interface RunView {
198
+ playbooks?: GeneralExecution["playbook"][];
199
+ execution?: GeneralExecution | null;
200
+ id: string;
201
+ threadId: string;
202
+ status: RunStatus;
203
+ engine: string;
204
+ stop: string | null;
205
+ error: string | null;
206
+ usage: {
207
+ inputTokens: number;
208
+ outputTokens: number;
209
+ } | null;
210
+ /** `usage` split into new input, cache read, cache write and output — what every surface shows. */
211
+ tokens?: import("./tokens.js").TokenBreakdown | null;
212
+ costUsd: number | null;
213
+ changes: number;
214
+ reverted: boolean;
215
+ keptOnRevert: number;
216
+ startedAt: number;
217
+ endedAt: number | null;
218
+ metrics?: import("./evaluation.js").TurnMetrics | null;
219
+ }
220
+ export interface CardView {
221
+ threadId: string;
222
+ id: string;
223
+ schema: string;
224
+ title: string;
225
+ data: unknown;
226
+ version: number;
227
+ updatedBy: Author | "human";
228
+ updatedAt: number;
229
+ }
230
+ export type UserInput = {
231
+ kind: "text";
232
+ text: string;
233
+ focus?: FocusRef[];
234
+ attachments?: import("./media.js").MediaRef[];
235
+ } | {
236
+ kind: "answer";
237
+ questionId: string;
238
+ value: string | string[];
239
+ };
240
+ export interface PendingView {
241
+ threadId: string;
242
+ kind: "ask" | "workflow" | "permission";
243
+ workflow: string | null;
244
+ question: {
245
+ id: string;
246
+ text: string;
247
+ options?: string[];
248
+ multiple?: boolean;
249
+ freeText?: boolean;
250
+ };
251
+ accepts: "options" | "text";
252
+ }
253
+ /** Pushed to the renderer over the `agent.event` broadcast. `progress` is transient — never persisted. */
254
+ export type AgentEvent = {
255
+ type: "permission-mode";
256
+ mode: string;
257
+ } | {
258
+ type: "run";
259
+ run: RunView;
260
+ } | {
261
+ type: "message";
262
+ message: ThreadMessage;
263
+ } | {
264
+ type: "delta";
265
+ runId: string;
266
+ text: string;
267
+ } | {
268
+ type: "progress";
269
+ runId: string;
270
+ activity?: string | null;
271
+ retry?: (RetryState & {
272
+ since: number;
273
+ }) | null;
274
+ usage?: {
275
+ inputTokens: number;
276
+ outputTokens: number;
277
+ };
278
+ elapsedMs?: number;
279
+ metrics?: RunView["metrics"];
280
+ } | {
281
+ type: "card";
282
+ card: CardView;
283
+ } | {
284
+ type: "pending";
285
+ pending: PendingView | null;
286
+ } | {
287
+ type: "done";
288
+ run: RunView;
289
+ };
290
+ /** A thread that stopped on a usage limit, waiting for it to lift. Kept on this machine only: it is this machine's clock that continues it. */
291
+ export interface ParkView {
292
+ threadId: string;
293
+ /** The run that hit the limit; null when a terminal session reported it. */
294
+ runId: string | null;
295
+ until: number;
296
+ /** Whether `until` came from the engine's own words, not a guess. */
297
+ known: boolean;
298
+ /** Tomrade continues the thread itself at `until`; otherwise the person is told it can go on. */
299
+ resume: boolean;
300
+ /** How many limited runs in a row this thread has had. */
301
+ attempt: number;
302
+ }
303
+ //# sourceMappingURL=thread.d.ts.map
package/dist/turn.d.ts ADDED
@@ -0,0 +1,165 @@
1
+ import type { LlmService } from "@tomradex/llm";
2
+ import type { RunHandle, ToolDescriptor } from "@tomradex/tools";
3
+ import type { Block, CardView, FocusRef, PendingView, ThreadMessage, UserInput } from "./thread.js";
4
+ import type { GeneralExecution, PlaybookIndexEntry, WorkflowInfo } from "./workflow.js";
5
+ /** What a turn is for. A `code` turn edits a repository and gets the engine's fuller powers; a `backlog` turn talks to the app through tools. */
6
+ export type TurnPurpose = "backlog" | "code" | "general";
7
+ /** A ticket the current one waits on that is neither done nor cancelled: context for the agent, never a lock on it. */
8
+ export interface OpenDependency {
9
+ key: string;
10
+ title: string;
11
+ status: string;
12
+ }
13
+ export interface TurnInput {
14
+ threadId: string;
15
+ projectId: string;
16
+ /** The project the conversation belongs to, named so the model knows where it is. */
17
+ project?: {
18
+ name: string;
19
+ keyPrefix: string;
20
+ };
21
+ purpose: TurnPurpose;
22
+ permissionMode?: string;
23
+ /** The ticket a code thread implements — the branch it must stay on, the key its commits must name. */
24
+ ticket?: {
25
+ id: string;
26
+ key: string;
27
+ title: string;
28
+ branch: string | null;
29
+ workType?: "coding" | "general";
30
+ body?: string | null;
31
+ completionPolicy?: "merge" | "human";
32
+ waitsOn?: OpenDependency[];
33
+ };
34
+ execution?: GeneralExecution;
35
+ input: UserInput;
36
+ /** Full persisted history, the triggering user message included. */
37
+ messages: ThreadMessage[];
38
+ cards: CardView[];
39
+ brief: string | null;
40
+ /** A question still waiting that the platform did NOT route — off-script input lands here. */
41
+ pending: PendingView | null;
42
+ /** What was on the user's screen when they sent the triggering message; deixis resolves here. */
43
+ focus?: FocusRef[];
44
+ /** Parsed engine options from settings (validated against Engine.options). */
45
+ options: unknown;
46
+ /** Where a code-running engine works: a worktree of the workstream's repository. Absent for a thread without one. */
47
+ cwd?: string;
48
+ /**
49
+ * What this project's Files hold, as the Files plugin renders it for an
50
+ * agent. Discovery only: it tells the engine what is there to look up, and
51
+ * says nothing about what may be read — every path reads either way.
52
+ */
53
+ filesIndex?: {
54
+ markdown: string;
55
+ digest: string;
56
+ };
57
+ /** The project's rules, as the person wrote them: every engine follows them. */
58
+ rules?: string;
59
+ /** Ephemeral subprocess credentials supplied by plugins; never persist or include in prompts. */
60
+ env?: Record<string, string>;
61
+ workdir?: {
62
+ remote: string;
63
+ branch: string;
64
+ worktree: string | null;
65
+ };
66
+ /** The project's workstream repositories and where they are on this machine (null: no checkout here), for a main conversation to read. */
67
+ repositories?: {
68
+ workstream: string;
69
+ name: string;
70
+ path: string | null;
71
+ defaultBranch: string;
72
+ }[];
73
+ }
74
+ /** A model request failed and the engine is trying again; `attempt` counts the retries so far. */
75
+ export interface RetryState {
76
+ attempt: number;
77
+ max?: number;
78
+ reason: string;
79
+ /** How long until the next try, when the engine says. */
80
+ delayMs?: number;
81
+ }
82
+ export interface TurnIO {
83
+ media?: {
84
+ read(id: string): import("./media.js").MediaContent;
85
+ path(id: string): string;
86
+ publish(upload: import("./media.js").MediaUpload, caption?: string): import("./media.js").MediaRef;
87
+ };
88
+ permissionMode?(mode: string): void;
89
+ /** Pause this live run for a human response. Null means cancelled, never approval. */
90
+ requestPermission?(text: string, options: string[], signal?: AbortSignal): Promise<string | null>;
91
+ /** Persist + broadcast one finished block. Text streams through delta() first. */
92
+ emit(block: Block): void;
93
+ delta(text: string): void;
94
+ /** Live token totals for the working line; call whenever the count grows. */
95
+ progress(usage: {
96
+ inputTokens?: number;
97
+ outputTokens?: number;
98
+ }): void;
99
+ /** A tool the engine runs itself is under way (Tomrade's own tools report through `tools`); null when it ends. */
100
+ activity?(label: string | null): void;
101
+ /** A model request failed and will be tried again; null once one goes through. */
102
+ retry?(state: RetryState | null): void;
103
+ tools: {
104
+ describe(): ToolDescriptor[];
105
+ /** Run-bound and ledgered; throws on refusal or failure so the engine can feed the error back. */
106
+ execute(name: string, args: unknown): Promise<unknown>;
107
+ /** What each kit is for, by kit id (see ToolDefinition.kit). */
108
+ kits?(): Record<string, string>;
109
+ /** These are all the tools the turn may use: the engine adds none of its own (the troubleshooter's scope). */
110
+ only?: boolean;
111
+ };
112
+ playbooks: {
113
+ index(): PlaybookIndexEntry[];
114
+ open(id: string): string | null;
115
+ };
116
+ workflows: {
117
+ list(): WorkflowInfo[];
118
+ };
119
+ llm: LlmService | null;
120
+ run: RunHandle;
121
+ signal: AbortSignal;
122
+ /** This engine's external session id from the previous turn, if it stored one. */
123
+ resume?: string;
124
+ /**
125
+ * Messages the person sent into this running turn, for an engine whose `steering` is `queue`. Call it where
126
+ * the model reads next (between tool calls); what it returns enters the transcript there.
127
+ */
128
+ takeSteers?(): string[];
129
+ /**
130
+ * For an engine whose `steering` is `forward` (its agent keeps its own queue: pi, Codex, Claude Code): `forward`
131
+ * gets each message sent into this turn and resolves whether the agent took it. Call
132
+ * `steered(id)` when the agent reads it; that is where it enters the transcript.
133
+ */
134
+ steerTo?(forward: (steer: {
135
+ id: string;
136
+ text: string;
137
+ }) => Promise<boolean>): void;
138
+ steered?(id: string): void;
139
+ /**
140
+ * For an engine that keeps its own session: the conversation so far, to lead the first prompt of a
141
+ * session it starts without `resume` (empty when there is nothing before this message).
142
+ */
143
+ recap?(): string;
144
+ }
145
+ export interface TurnResult {
146
+ stop: "end" | "asked" | "budget" | "cancelled" | "error";
147
+ usage?: {
148
+ inputTokens?: number;
149
+ outputTokens?: number;
150
+ };
151
+ costUsd?: number;
152
+ error?: string;
153
+ /** Everything else known about the failure (see describeFailure); shown folded under `error`. */
154
+ details?: string;
155
+ /** External session id to store on the thread for the next turn. */
156
+ resume?: string;
157
+ /** The session in `resume` no longer exists and nothing ran: Tomrade forgets it and runs the turn again in a new one. */
158
+ lostSession?: boolean;
159
+ metrics?: import("./evaluation.js").TurnMetrics;
160
+ /** The engine stopped on a usage or rate limit; `until` is when it lifts, when the engine said. */
161
+ limit?: {
162
+ until: number | null;
163
+ };
164
+ }
165
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1,127 @@
1
+ import type { z } from "zod";
2
+ import type { LlmService } from "@tomradex/llm";
3
+ import type { RunHandle } from "@tomradex/tools";
4
+ import type { Block } from "./thread.js";
5
+ import type { TurnIO } from "./turn.js";
6
+ export interface WorkflowInfo {
7
+ id: string;
8
+ title: string;
9
+ description: string;
10
+ /** Whether the workflow can start with no arguments — what a menu can offer directly. */
11
+ canStartEmpty: boolean;
12
+ }
13
+ /** Where a playbook came from. Same id from several sources is a conflict the user can settle in settings. */
14
+ export type PlaybookSource = "plugin" | "user" | "project";
15
+ /** Recommendations and prerequisites, never permission grants. */
16
+ interface PlaybookRequirements {
17
+ workTypes?: ("coding" | "general")[];
18
+ requires?: string[];
19
+ optionalCapabilities?: string[];
20
+ outputs?: string[];
21
+ }
22
+ export interface GeneralExecution {
23
+ deliveryId: string;
24
+ playbook: {
25
+ id: string;
26
+ source: PlaybookSource;
27
+ digest: string;
28
+ body: string;
29
+ requires: string[];
30
+ };
31
+ }
32
+ /** A methodology document: the `when` line lives in every prompt, the body loads on demand. */
33
+ export interface Playbook extends PlaybookRequirements {
34
+ id: string;
35
+ title: string;
36
+ when: string;
37
+ tools: string[];
38
+ body: string;
39
+ }
40
+ export interface PlaybookIndexEntry extends PlaybookRequirements {
41
+ id: string;
42
+ title: string;
43
+ when: string;
44
+ tools: string[];
45
+ /** The source that won for this id. */
46
+ source: PlaybookSource;
47
+ /** Sources that also carry this id and lost — non-empty means a conflict. */
48
+ shadows: PlaybookSource[];
49
+ }
50
+ export interface WorkflowQuestion {
51
+ text: string;
52
+ options?: string[];
53
+ multiple?: boolean;
54
+ freeText?: boolean;
55
+ }
56
+ export type WorkflowTransition<S> = {
57
+ next: string;
58
+ state: S;
59
+ } | {
60
+ ask: WorkflowQuestion;
61
+ state: S;
62
+ } | {
63
+ done: unknown;
64
+ };
65
+ export interface WorkflowIO {
66
+ emit(block: Block): void;
67
+ tools: TurnIO["tools"];
68
+ llm: LlmService | null;
69
+ run: RunHandle;
70
+ /** Aborts when the run is stopped or runs out of time; a step's model calls end with it. */
71
+ signal: AbortSignal;
72
+ /** What a step is doing while no tool runs, for the run's live line; null clears it. */
73
+ activity(label: string | null): void;
74
+ }
75
+ /**
76
+ * A resumable, typed step machine, exposed to every engine as a tool. Control
77
+ * flow is code; steps may still call the worker model for content. A step that
78
+ * returns `ask` suspends the workflow — the platform persists the state and
79
+ * routes the user's next on-script input straight back into the same step.
80
+ */
81
+ export interface WorkflowSpec<I = unknown, S = unknown> {
82
+ /** Also the tool name — snake_case, globally unique. */
83
+ id: string;
84
+ title: string;
85
+ description: string;
86
+ input: z.ZodType<I>;
87
+ state: z.ZodType<S>;
88
+ entry: string;
89
+ start(input: I, io: WorkflowIO): S | Promise<S>;
90
+ steps: Record<string, (state: S, io: WorkflowIO, answer?: string | string[]) => WorkflowTransition<S> | Promise<WorkflowTransition<S>>>;
91
+ /** What counts as on-script input while suspended: one of the offered options, or any text. */
92
+ accepts?: "options" | "text";
93
+ /** Offered only to engines without a model (Engine.literal); a model holds this conversation itself, in the user's language. */
94
+ literal?: boolean;
95
+ }
96
+ /**
97
+ * Consulted when a turn ends, while its run is still open, so whatever it starts
98
+ * writes into the same run and asks like any other workflow. `canAsk` is false
99
+ * when the run already ends on a question, failed or was cancelled; a follow-up
100
+ * then only notes what it still owes the thread.
101
+ */
102
+ export interface TurnFollowUp {
103
+ id: string;
104
+ after(turn: {
105
+ projectId: string;
106
+ threadId: string;
107
+ runId: string;
108
+ canAsk: boolean;
109
+ }): FollowUpStart | null | Promise<FollowUpStart | null>;
110
+ }
111
+ export interface FollowUpStart {
112
+ workflow: string;
113
+ args?: unknown;
114
+ }
115
+ /** What a suspending tool call returns; engines end their turn when they see one. */
116
+ export interface Suspended {
117
+ __suspended: true;
118
+ question: {
119
+ id: string;
120
+ text: string;
121
+ options?: string[];
122
+ };
123
+ note: string;
124
+ }
125
+ export declare const isSuspended: (value: unknown) => value is Suspended;
126
+ export {};
127
+ //# sourceMappingURL=workflow.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomradex/agent",
3
- "version": "0.16.3",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "author": "Haoyuan Tang",
6
6
  "engines": {
@@ -27,16 +27,17 @@
27
27
  "dist"
28
28
  ],
29
29
  "dependencies": {
30
- "@tomradex/db": "0.16.3",
31
- "@tomradex/domain": "0.2.19",
32
- "@tomradex/fields": "0.16.3",
33
- "@tomradex/hub": "0.16.3",
34
- "@tomradex/ipc": "0.16.3",
35
- "@tomradex/llm": "0.16.3",
36
- "@tomradex/oplog": "0.16.3",
37
- "@tomradex/settings": "0.16.3",
38
- "@tomradex/tools": "0.16.3",
39
- "@tomradex/write": "0.16.3",
30
+ "@tomradex/contracts": "0.17.0",
31
+ "@tomradex/db": "0.17.0",
32
+ "@tomradex/domain": "0.2.24",
33
+ "@tomradex/fields": "0.17.0",
34
+ "@tomradex/hub": "0.17.0",
35
+ "@tomradex/ipc": "0.17.0",
36
+ "@tomradex/llm": "0.17.0",
37
+ "@tomradex/oplog": "0.17.0",
38
+ "@tomradex/settings": "0.17.0",
39
+ "@tomradex/tools": "0.17.0",
40
+ "@tomradex/write": "0.17.0",
40
41
  "cordis": "4.0.0-rc.8",
41
42
  "zod": "^4.4.3"
42
43
  },