@capybara-hq/kernel 0.1.0-rc.3 → 0.1.0-rc.4
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.
- package/dist/admin/admin.controller.d.ts +9 -4
- package/dist/admin/admin.controller.js +13 -4
- package/dist/admin/extensions.controller.d.ts +8 -1
- package/dist/admin/extensions.controller.js +22 -2
- package/dist/agents/agent-lookup.d.ts +3 -0
- package/dist/agents/agent-lookup.js +10 -0
- package/dist/agents/agent-profile.service.d.ts +29 -0
- package/dist/agents/agent-profile.service.js +117 -0
- package/dist/agents/agents.controller.d.ts +93 -3
- package/dist/agents/agents.controller.js +114 -4
- package/dist/agents/agents.module.js +6 -2
- package/dist/agents/agents.service.d.ts +13 -5
- package/dist/agents/agents.service.js +30 -7
- package/dist/agents/definition.d.ts +4 -0
- package/dist/agents/definition.js +10 -0
- package/dist/agents/instruction-files.d.ts +60 -0
- package/dist/agents/instruction-files.js +111 -0
- package/dist/agents/models.controller.d.ts +9 -3
- package/dist/agents/models.controller.js +19 -4
- package/dist/agents/profile.d.ts +96 -0
- package/dist/agents/profile.js +51 -0
- package/dist/agents/readiness.controller.d.ts +26 -0
- package/dist/agents/readiness.controller.js +56 -0
- package/dist/agents/templates.js +1 -1
- package/dist/background/background.controller.d.ts +38 -0
- package/dist/background/background.controller.js +87 -0
- package/dist/background/background.module.d.ts +10 -0
- package/dist/background/background.module.js +32 -0
- package/dist/background/finish.d.ts +43 -0
- package/dist/background/finish.js +94 -0
- package/dist/background/launch-lock.d.ts +14 -0
- package/dist/background/launch-lock.js +39 -0
- package/dist/background/launch.d.ts +36 -0
- package/dist/background/launch.js +70 -0
- package/dist/background/poll.job.d.ts +53 -0
- package/dist/background/poll.job.js +182 -0
- package/dist/background/render.d.ts +23 -0
- package/dist/background/render.js +48 -0
- package/dist/background/session.d.ts +37 -0
- package/dist/background/session.js +66 -0
- package/dist/background/status.d.ts +35 -0
- package/dist/background/status.js +57 -0
- package/dist/background/stop.d.ts +62 -0
- package/dist/background/stop.js +225 -0
- package/dist/background/store.d.ts +150 -0
- package/dist/background/store.js +351 -0
- package/dist/background/store.module.d.ts +6 -0
- package/dist/background/store.module.js +22 -0
- package/dist/background/tasks.service.d.ts +67 -0
- package/dist/background/tasks.service.js +186 -0
- package/dist/background/wake-rules.d.ts +57 -0
- package/dist/background/wake-rules.js +83 -0
- package/dist/background/wake.d.ts +28 -0
- package/dist/background/wake.js +57 -0
- package/dist/catalog/claude/agents.js +2 -0
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/plugin-import.js +1 -1
- package/dist/catalog/plugin-upgrade.js +1 -1
- package/dist/channels/cards.js +1 -1
- package/dist/channels/connections.service.d.ts +16 -1
- package/dist/channels/connections.service.js +38 -4
- package/dist/channels/threads.d.ts +5 -0
- package/dist/channels/threads.js +8 -0
- package/dist/config/schema.d.ts +18 -0
- package/dist/config/schema.js +102 -0
- package/dist/conversations/access.js +2 -0
- package/dist/conversations/attachments.service.d.ts +30 -0
- package/dist/conversations/attachments.service.js +130 -5
- package/dist/conversations/conversations.controller.d.ts +36 -5
- package/dist/conversations/conversations.controller.js +62 -12
- package/dist/conversations/conversations.module.js +3 -0
- package/dist/conversations/conversations.service.d.ts +44 -2
- package/dist/conversations/conversations.service.js +140 -12
- package/dist/conversations/event-log.d.ts +1 -1
- package/dist/conversations/event-log.js +10 -1
- package/dist/conversations/events.d.ts +58 -0
- package/dist/conversations/events.js +57 -2
- package/dist/conversations/queue.d.ts +3 -1
- package/dist/conversations/queue.js +13 -2
- package/dist/conversations/queue.service.js +3 -1
- package/dist/conversations/titles.d.ts +7 -0
- package/dist/conversations/titles.js +15 -0
- package/dist/db/kernel-schema.d.ts +58 -2
- package/dist/db/pool.d.ts +5 -0
- package/dist/db/pool.js +3 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +8 -0
- package/dist/evals/run-replay.d.ts +5 -2
- package/dist/evals/run-replay.js +30 -7
- package/dist/evals/step-replay.js +12 -6
- package/dist/evals/trajectory.js +1 -1
- package/dist/extensions/admin-ops.js +2 -0
- package/dist/extensions/admin-view.d.ts +3 -1
- package/dist/extensions/admin-view.js +10 -2
- package/dist/extensions/blobs.js +1 -0
- package/dist/extensions/config-issues.d.ts +13 -1
- package/dist/extensions/config-issues.js +22 -5
- package/dist/extensions/config-json-schema.d.ts +43 -7
- package/dist/extensions/config-json-schema.js +124 -58
- package/dist/extensions/deployment-config-store.d.ts +6 -0
- package/dist/extensions/deployment-config.d.ts +50 -3
- package/dist/extensions/deployment-config.js +125 -28
- package/dist/extensions/display-name.d.ts +22 -0
- package/dist/extensions/display-name.js +77 -0
- package/dist/extensions/extensions.module.js +6 -0
- package/dist/extensions/host.d.ts +26 -1
- package/dist/extensions/host.js +82 -9
- package/dist/extensions/org-config.controller.d.ts +11 -3
- package/dist/extensions/org-config.controller.js +14 -6
- package/dist/extensions/org-config.d.ts +6 -0
- package/dist/extensions/org-config.js +12 -13
- package/dist/extensions/package.d.ts +14 -1
- package/dist/extensions/package.js +51 -2
- package/dist/extensions/permission-view.d.ts +3 -0
- package/dist/extensions/point-defaults.controller.d.ts +34 -0
- package/dist/extensions/point-defaults.controller.js +71 -0
- package/dist/extensions/point-defaults.d.ts +64 -0
- package/dist/extensions/point-defaults.js +206 -0
- package/dist/extensions/registry.d.ts +1 -1
- package/dist/extensions/registry.js +2 -0
- package/dist/http/api.module.js +8 -0
- package/dist/http/nest-logger.d.ts +6 -5
- package/dist/http/nest-logger.js +24 -10
- package/dist/identity/auth.controller.js +9 -5
- package/dist/identity/cookies.d.ts +20 -5
- package/dist/identity/cookies.js +44 -6
- package/dist/identity/me.controller.d.ts +5 -1
- package/dist/identity/me.controller.js +12 -3
- package/dist/identity/sessions.d.ts +12 -0
- package/dist/identity/sessions.js +45 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/config/kernel-config.service.js +6 -0
- package/dist/infra/database/database.module.js +1 -0
- package/dist/infra/events/kernel-events.d.ts +8 -0
- package/dist/infra/lifecycle/process-guards.js +4 -0
- package/dist/infra/logging/logging.module.d.ts +4 -2
- package/dist/infra/logging/logging.module.js +24 -6
- package/dist/infra/queue/redis-connection.d.ts +6 -0
- package/dist/infra/queue/redis-connection.js +7 -0
- package/dist/infra/redis/redis.module.js +6 -2
- package/dist/infra/tokens.d.ts +6 -1
- package/dist/infra/tokens.js +6 -1
- package/dist/log.d.ts +13 -1
- package/dist/log.js +93 -17
- package/dist/models/purposes.d.ts +23 -0
- package/dist/models/purposes.js +31 -10
- package/dist/orgs/offboarding.d.ts +3 -1
- package/dist/orgs/offboarding.js +26 -16
- package/dist/orgs/settings.service.d.ts +5 -0
- package/dist/orgs/settings.service.js +13 -2
- package/dist/roles/worker.module.js +6 -0
- package/dist/runtime/context/build.d.ts +9 -7
- package/dist/runtime/context/build.js +27 -19
- package/dist/runtime/context/claude-tools.d.ts +2 -1
- package/dist/runtime/context/claude-tools.js +5 -4
- package/dist/runtime/context/compaction.js +4 -0
- package/dist/runtime/langgraph-loop.js +3 -2
- package/dist/runtime/loop.d.ts +4 -3
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -5
- package/dist/runtime/model-call.d.ts +17 -1
- package/dist/runtime/model-call.js +3 -2
- package/dist/runtime/providers.d.ts +6 -0
- package/dist/runtime/providers.js +8 -0
- package/dist/runtime/run-queue.d.ts +25 -2
- package/dist/runtime/run-queue.js +129 -33
- package/dist/runtime/run-worker.js +16 -3
- package/dist/runtime/runtime.module.js +8 -3
- package/dist/runtime/step-log.d.ts +6 -0
- package/dist/runtime/step-log.js +16 -2
- package/dist/runtime/subagents/definition.js +3 -1
- package/dist/runtime/usage-report.d.ts +2 -1
- package/dist/runtime/usage-report.js +2 -1
- package/dist/runtime/waits.d.ts +8 -2
- package/dist/runtime/waits.js +37 -11
- package/dist/sandbox/sandbox.module.js +6 -1
- package/dist/sandbox/sandbox.service.d.ts +61 -3
- package/dist/sandbox/sandbox.service.js +141 -7
- package/dist/tools/approvals.service.js +2 -0
- package/dist/tools/builtin-names.d.ts +20 -2
- package/dist/tools/builtin-names.js +29 -2
- package/dist/tools/builtins/builtin-tools.d.ts +15 -1
- package/dist/tools/builtins/builtin-tools.js +34 -2
- package/dist/tools/builtins/sandbox/bash.d.ts +1 -1
- package/dist/tools/builtins/sandbox/bash.js +230 -3
- package/dist/tools/builtins/sandbox/common.d.ts +27 -4
- package/dist/tools/builtins/sandbox/common.js +7 -3
- package/dist/tools/builtins/sandbox/deliver.js +1 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -0
- package/dist/tools/builtins/sandbox/index.js +6 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
- package/dist/tools/builtins/sandbox/task-output.js +304 -0
- package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
- package/dist/tools/builtins/sandbox/task-stop.js +112 -0
- package/dist/tools/builtins/web/errors.d.ts +8 -0
- package/dist/tools/builtins/web/errors.js +53 -0
- package/dist/tools/builtins/web/extract.d.ts +62 -0
- package/dist/tools/builtins/web/extract.js +267 -0
- package/dist/tools/builtins/web/fetch.d.ts +55 -0
- package/dist/tools/builtins/web/fetch.js +174 -0
- package/dist/tools/builtins/web/index.d.ts +13 -0
- package/dist/tools/builtins/web/index.js +12 -0
- package/dist/tools/builtins/web/page-cache.d.ts +25 -0
- package/dist/tools/builtins/web/page-cache.js +77 -0
- package/dist/tools/builtins/web/search.d.ts +25 -0
- package/dist/tools/builtins/web/search.js +209 -0
- package/dist/tools/builtins/web/url.d.ts +17 -0
- package/dist/tools/builtins/web/url.js +43 -0
- package/dist/tools/oauth/flow-paths.d.ts +1 -1
- package/dist/tools/oauth/flow-paths.js +1 -1
- package/dist/tools/pipeline.js +15 -0
- package/dist/tools/registry.d.ts +22 -3
- package/dist/tools/registry.js +26 -11
- package/dist/tools/tools.module.js +12 -0
- package/dist/tools/validate.js +1 -1
- package/package.json +3 -3
- package/src/migrations/0064_account_last_org.sql +7 -0
- package/src/migrations/0065_conversation_archive.sql +8 -0
- package/src/migrations/0066_extension_display_name.sql +4 -0
- package/src/migrations/0067_agent_profiles.sql +20 -0
- package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
- package/src/migrations/0069_attachments_agent_idx.sql +3 -0
- package/src/migrations/0070_background_tasks.sql +37 -0
- package/src/migrations/0071_background_tasks_reserve.sql +6 -0
- package/src/migrations/0072_background_tasks_stopping.sql +5 -0
- package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
|
@@ -11,6 +11,8 @@ import { randomUUID } from "node:crypto";
|
|
|
11
11
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
12
|
import { sql } from "kysely";
|
|
13
13
|
import { AgentLookup, visibleTo } from "../agents/agent-lookup.js";
|
|
14
|
+
import { BackgroundTaskStopper } from "../background/stop.js";
|
|
15
|
+
import { unbindConversation } from "../channels/threads.js";
|
|
14
16
|
import { ApiError } from "../errors.js";
|
|
15
17
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
16
18
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
@@ -25,6 +27,27 @@ import { AttachmentsService } from "./attachments.service.js";
|
|
|
25
27
|
import { EventLog } from "./event-log.js";
|
|
26
28
|
import { userMessageIdemKey } from "./idem-keys.js";
|
|
27
29
|
import { NO_SELECTION, planSelection, readSelection } from "./selection.js";
|
|
30
|
+
/** Escapes `\`, `%` and `_` so user text is matched literally by `LIKE … ESCAPE '\'`. */
|
|
31
|
+
export function escapeLike(s) {
|
|
32
|
+
return s.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SQL for the activity of the conversation row aliased/named `ref` (a correlated subquery).
|
|
36
|
+
* A run waiting on its sub-agents (status waiting with `wait_expires_at`, set only for a
|
|
37
|
+
* `subagents` wait) is still working, so it reports `running`; only a wait for a person
|
|
38
|
+
* (approval, input) reports `waiting`.
|
|
39
|
+
*/
|
|
40
|
+
function activitySql(ref) {
|
|
41
|
+
const id = sql.ref(`${ref}.id`);
|
|
42
|
+
return sql `(
|
|
43
|
+
SELECT CASE
|
|
44
|
+
WHEN lr.status IN ('queued', 'running') THEN 'running'
|
|
45
|
+
WHEN lr.status = 'waiting' AND lr.wait_expires_at IS NOT NULL THEN 'running'
|
|
46
|
+
WHEN lr.status = 'waiting' THEN 'waiting' END
|
|
47
|
+
FROM capybara.runs lr WHERE lr.conversation_id = ${id}
|
|
48
|
+
ORDER BY lr.created_at DESC, lr.id DESC LIMIT 1
|
|
49
|
+
)`;
|
|
50
|
+
}
|
|
28
51
|
const notFound = () => new ApiError("conversation.not_found", "Conversation not found");
|
|
29
52
|
export { userMessageIdemKey } from "./idem-keys.js";
|
|
30
53
|
const COLUMNS = [
|
|
@@ -40,6 +63,7 @@ const COLUMNS = [
|
|
|
40
63
|
"updated_at",
|
|
41
64
|
"parent_conversation_id",
|
|
42
65
|
"subagent_type",
|
|
66
|
+
"archived_at",
|
|
43
67
|
];
|
|
44
68
|
function toView(r) {
|
|
45
69
|
return {
|
|
@@ -55,6 +79,8 @@ function toView(r) {
|
|
|
55
79
|
updatedAt: r.updated_at,
|
|
56
80
|
parentConversationId: r.parent_conversation_id,
|
|
57
81
|
subagentType: r.subagent_type,
|
|
82
|
+
archivedAt: r.archived_at,
|
|
83
|
+
activity: r.activity ?? null,
|
|
58
84
|
};
|
|
59
85
|
}
|
|
60
86
|
/** The longest conversation title (characters, after trimming). */
|
|
@@ -90,6 +116,7 @@ let ConversationsService = class ConversationsService {
|
|
|
90
116
|
content;
|
|
91
117
|
attachments;
|
|
92
118
|
invoker;
|
|
119
|
+
background;
|
|
93
120
|
txHost;
|
|
94
121
|
constructor(db, eventLog, agents, access,
|
|
95
122
|
/** Starts and stops the conversation's runs. */
|
|
@@ -101,7 +128,9 @@ let ConversationsService = class ConversationsService {
|
|
|
101
128
|
/** Binds uploaded files to the conversation in the message's transaction. */
|
|
102
129
|
attachments,
|
|
103
130
|
/** Renders a person's skill invocation (phase D). */
|
|
104
|
-
invoker,
|
|
131
|
+
invoker,
|
|
132
|
+
/** Stops a deleted conversation's background tasks. */
|
|
133
|
+
background, txHost) {
|
|
105
134
|
this.db = db;
|
|
106
135
|
this.eventLog = eventLog;
|
|
107
136
|
this.agents = agents;
|
|
@@ -112,6 +141,7 @@ let ConversationsService = class ConversationsService {
|
|
|
112
141
|
this.content = content;
|
|
113
142
|
this.attachments = attachments;
|
|
114
143
|
this.invoker = invoker;
|
|
144
|
+
this.background = background;
|
|
115
145
|
this.txHost = txHost;
|
|
116
146
|
}
|
|
117
147
|
/**
|
|
@@ -139,6 +169,8 @@ let ConversationsService = class ConversationsService {
|
|
|
139
169
|
* message's `clientMessageId` (`create_request_id`), so a retried send returns the same
|
|
140
170
|
* conversation and event with `created: false`. An agent the caller cannot use →
|
|
141
171
|
* `agent.not_found` and nothing is written. Queues a run afterwards, like `postUserMessage`.
|
|
172
|
+
* `events` are every event the create wrote, in seq order from seq 1 (a `model_switched` before
|
|
173
|
+
* the message when the send switched), so a client can seed the new conversation's log whole.
|
|
142
174
|
*/
|
|
143
175
|
async createWithMessage(orgId, accountId, agentId, input) {
|
|
144
176
|
const agent = await this.agents.get(orgId, agentId, accountId);
|
|
@@ -149,8 +181,9 @@ let ConversationsService = class ConversationsService {
|
|
|
149
181
|
const extra = await this.resolveExtra(orgId, accountId, input);
|
|
150
182
|
const invoked = await this.prepareSkill(orgId, agentId, undefined, agent.definition, input);
|
|
151
183
|
const id = randomUUID();
|
|
152
|
-
const
|
|
153
|
-
|
|
184
|
+
const all = await this.createIn(id, orgId, accountId, agentId, input, next, extra, invoked);
|
|
185
|
+
const written = all?.at(-1);
|
|
186
|
+
if (!all || !written) {
|
|
154
187
|
const raced = await this.findCreated(orgId, accountId, agentId, input.clientMessageId);
|
|
155
188
|
if (!raced)
|
|
156
189
|
throw new ApiError("conflict", "The conversation is being created; retry");
|
|
@@ -158,7 +191,7 @@ let ConversationsService = class ConversationsService {
|
|
|
158
191
|
}
|
|
159
192
|
await this.eventLog.notify(id, written.seq);
|
|
160
193
|
await this.runs.ensureRun(id);
|
|
161
|
-
return { conversation: await this.rowView(id), event: written, created: true };
|
|
194
|
+
return { conversation: await this.rowView(id), event: written, events: all, created: true };
|
|
162
195
|
}
|
|
163
196
|
/** The conversation row and its first message (undefined: a concurrent retry committed first). */
|
|
164
197
|
// its own transaction: the caller notifies and publishes after the commit
|
|
@@ -206,8 +239,10 @@ let ConversationsService = class ConversationsService {
|
|
|
206
239
|
const event = await this.eventLog.findByIdem(row.id, userMessageIdemKey(clientMessageId));
|
|
207
240
|
if (!event)
|
|
208
241
|
return null;
|
|
242
|
+
// what the first send wrote: the log from its start up to the message
|
|
243
|
+
const events = (await this.eventLog.readAfter(row.id, 0, event.seq)).filter((e) => e.seq <= event.seq);
|
|
209
244
|
await this.runs.ensureRun(row.id);
|
|
210
|
-
return { conversation: toView(row), event, created: false };
|
|
245
|
+
return { conversation: toView(row), event, events, created: false };
|
|
211
246
|
}
|
|
212
247
|
async rowView(id) {
|
|
213
248
|
return toView(await this.db
|
|
@@ -231,6 +266,58 @@ let ConversationsService = class ConversationsService {
|
|
|
231
266
|
await this.eventLog.publishChanged(conversationId, "title");
|
|
232
267
|
return toView(row);
|
|
233
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Archives or unarchives a conversation (owner only, as {@link rename}). An archived
|
|
271
|
+
* conversation leaves the default lists but stays readable. Publishes `changed: archived`.
|
|
272
|
+
*/
|
|
273
|
+
async setArchived(orgId, conversationId, accountId, archived) {
|
|
274
|
+
await this.writable(orgId, conversationId, accountId);
|
|
275
|
+
const row = await this.db
|
|
276
|
+
.updateTable("conversations")
|
|
277
|
+
.set({ archived_at: archived ? sql `now()` : null })
|
|
278
|
+
.where("id", "=", conversationId)
|
|
279
|
+
.returning(COLUMNS)
|
|
280
|
+
.executeTakeFirstOrThrow();
|
|
281
|
+
await this.eventLog.publishChanged(conversationId, "archived");
|
|
282
|
+
return toView(row);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Soft-deletes a conversation (owner only; a sub-agent's conversation is `conversation.read_only`):
|
|
286
|
+
* stops an active run, hides the conversation everywhere for its owner and drops its channel
|
|
287
|
+
* bindings, then stops every running background task in its sandbox (its sub-agents' too;
|
|
288
|
+
* outside the delete's transaction, a task whose sandbox cannot be reached ends `lost` with
|
|
289
|
+
* `kill_pending`). The tasks are stopped after the conversation is marked deleted, so no finish
|
|
290
|
+
* (the stops' own, or one the poller writes meanwhile) can wake a run on it: `ensureRun` refuses a
|
|
291
|
+
* deleted conversation (the stops' finishes carry `stoppedBy: "deletion"` as well). The event
|
|
292
|
+
* log, runs and trajectory stay (enterprise admins still see them).
|
|
293
|
+
*/
|
|
294
|
+
async remove(orgId, conversationId, accountId) {
|
|
295
|
+
await this.writable(orgId, conversationId, accountId);
|
|
296
|
+
try {
|
|
297
|
+
await this.abort(orgId, conversationId, accountId);
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
if (!(e instanceof ApiError && e.code === "run.not_active"))
|
|
301
|
+
throw e;
|
|
302
|
+
}
|
|
303
|
+
await this.markDeleted(conversationId);
|
|
304
|
+
await this.background.stopForDeletedConversation(conversationId);
|
|
305
|
+
await this.eventLog.publishChanged(conversationId, "deleted");
|
|
306
|
+
}
|
|
307
|
+
// the publish happens after the commit, in the caller
|
|
308
|
+
async markDeleted(conversationId) {
|
|
309
|
+
await this.db
|
|
310
|
+
.updateTable("conversations")
|
|
311
|
+
.set({ deleted_at: sql `now()` })
|
|
312
|
+
.where("id", "=", conversationId)
|
|
313
|
+
.execute();
|
|
314
|
+
// Queued messages must not start a run on a deleted conversation
|
|
315
|
+
await this.db
|
|
316
|
+
.deleteFrom("conversation_queue")
|
|
317
|
+
.where("conversation_id", "=", conversationId)
|
|
318
|
+
.execute();
|
|
319
|
+
await unbindConversation(this.db.db, conversationId);
|
|
320
|
+
}
|
|
234
321
|
/** 读会话:非所有者、或 agent 已对所有者不可用 → `conversation.not_found`。 */
|
|
235
322
|
async get(orgId, conversationId, viewerAccountId) {
|
|
236
323
|
const { kind: _kind, ...row } = await this.readable(orgId, conversationId, viewerAccountId);
|
|
@@ -255,6 +342,7 @@ let ConversationsService = class ConversationsService {
|
|
|
255
342
|
.where("id", "=", conversationId)
|
|
256
343
|
.where("org_id", "=", orgId)
|
|
257
344
|
.where("account_id", "=", viewerAccountId)
|
|
345
|
+
.where("deleted_at", "is", null)
|
|
258
346
|
// Hidden eval replay conversations (kind 'eval') are not reachable here (only the trajectory
|
|
259
347
|
// viewer shows them); a sub-agent's conversation is, for its owner (sub-agents spec §6.1)
|
|
260
348
|
.where("kind", "in", ["chat", "subagent"])
|
|
@@ -284,10 +372,13 @@ let ConversationsService = class ConversationsService {
|
|
|
284
372
|
let q = this.db
|
|
285
373
|
.selectFrom("conversations")
|
|
286
374
|
.select(COLUMNS)
|
|
375
|
+
.select(activitySql("conversations").as("activity"))
|
|
287
376
|
.where("org_id", "=", orgId)
|
|
288
377
|
.where("account_id", "=", accountId)
|
|
289
378
|
// 评测回放的隐藏对话(kind = 'eval')不在「我的对话」里
|
|
290
379
|
.where("kind", "=", "chat")
|
|
380
|
+
.where("deleted_at", "is", null)
|
|
381
|
+
.where("archived_at", opts.archived === true ? "is not" : "is", null)
|
|
291
382
|
.where((eb) => eb.exists(eb
|
|
292
383
|
.selectFrom("agents")
|
|
293
384
|
.select(sql `1`.as("one"))
|
|
@@ -299,6 +390,12 @@ let ConversationsService = class ConversationsService {
|
|
|
299
390
|
.limit(opts.limit);
|
|
300
391
|
if (opts.agentId !== undefined)
|
|
301
392
|
q = q.where("agent_id", "=", opts.agentId);
|
|
393
|
+
if (opts.q !== undefined) {
|
|
394
|
+
q = q.where(sql `conversations.title ILIKE ${`%${escapeLike(opts.q)}%`} ESCAPE '\\'`);
|
|
395
|
+
}
|
|
396
|
+
if (opts.state !== undefined) {
|
|
397
|
+
q = q.where(sql `${activitySql("conversations")} = ${opts.state}`);
|
|
398
|
+
}
|
|
302
399
|
if (opts.after !== undefined) {
|
|
303
400
|
const after = opts.after;
|
|
304
401
|
q = q.where((eb) => sql `(${eb.ref("updated_at")}, ${eb.ref("id")}) < (
|
|
@@ -322,6 +419,11 @@ let ConversationsService = class ConversationsService {
|
|
|
322
419
|
.where("agents.org_id", "=", orgId)
|
|
323
420
|
.where("agents.deleted_at", "is", null)
|
|
324
421
|
.where(visibleTo(accountId));
|
|
422
|
+
const archivedFilter = opts.archived === true ? sql `c.archived_at IS NOT NULL` : sql `c.archived_at IS NULL`;
|
|
423
|
+
const searchFilter = opts.q === undefined
|
|
424
|
+
? sql ``
|
|
425
|
+
: sql `AND c.title ILIKE ${`%${escapeLike(opts.q)}%`} ESCAPE '\\'`;
|
|
426
|
+
const stateFilter = opts.state === undefined ? sql `` : sql `AND ${activitySql("c")} = ${opts.state}`;
|
|
325
427
|
const cursor = opts.after === undefined
|
|
326
428
|
? sql ``
|
|
327
429
|
: sql `WHERE (g.latest_at, g.agent_id) < (
|
|
@@ -332,6 +434,7 @@ let ConversationsService = class ConversationsService {
|
|
|
332
434
|
SELECT c.agent_id, max(c.updated_at) AS latest_at
|
|
333
435
|
FROM capybara.conversations c
|
|
334
436
|
WHERE c.org_id = ${orgId} AND c.account_id = ${accountId} AND c.kind = 'chat'
|
|
437
|
+
AND c.deleted_at IS NULL AND ${archivedFilter} ${searchFilter} ${stateFilter}
|
|
335
438
|
AND c.agent_id IN ${visibleAgents}
|
|
336
439
|
GROUP BY c.agent_id
|
|
337
440
|
), page AS (
|
|
@@ -342,15 +445,18 @@ let ConversationsService = class ConversationsService {
|
|
|
342
445
|
)
|
|
343
446
|
SELECT page.agent_id, page.latest_at, v.definition ->> 'name' AS agent_name,
|
|
344
447
|
cs.id, cs.org_id, cs.account_id, cs.title, cs.title_source, cs.selection, cs.last_seq,
|
|
345
|
-
cs.created_at, cs.updated_at, cs.parent_conversation_id, cs.subagent_type
|
|
448
|
+
cs.created_at, cs.updated_at, cs.parent_conversation_id, cs.subagent_type,
|
|
449
|
+
cs.archived_at, cs.activity
|
|
346
450
|
FROM page
|
|
347
451
|
JOIN capybara.agents a ON a.id = page.agent_id
|
|
348
452
|
JOIN capybara.agent_versions v ON v.agent_id = a.id AND v.version = a.current_version
|
|
349
453
|
CROSS JOIN LATERAL (
|
|
350
454
|
SELECT c.id, c.org_id, c.account_id, c.title, c.title_source, c.selection, c.last_seq,
|
|
351
|
-
c.created_at, c.updated_at, c.parent_conversation_id, c.subagent_type
|
|
455
|
+
c.created_at, c.updated_at, c.parent_conversation_id, c.subagent_type,
|
|
456
|
+
c.archived_at, ${activitySql("c")} AS activity
|
|
352
457
|
FROM capybara.conversations c
|
|
353
458
|
WHERE c.org_id = ${orgId} AND c.account_id = ${accountId} AND c.kind = 'chat'
|
|
459
|
+
AND c.deleted_at IS NULL AND ${archivedFilter} ${searchFilter} ${stateFilter}
|
|
354
460
|
AND c.agent_id = page.agent_id
|
|
355
461
|
ORDER BY c.updated_at DESC, c.id DESC
|
|
356
462
|
LIMIT ${opts.perGroup + 1}
|
|
@@ -377,7 +483,14 @@ let ConversationsService = class ConversationsService {
|
|
|
377
483
|
}
|
|
378
484
|
const page = groups.slice(0, opts.limit);
|
|
379
485
|
const next = groups.length > opts.limit ? (page.at(-1)?.agent.id ?? null) : null;
|
|
380
|
-
|
|
486
|
+
// The archived entry's count ignores `q` and `state`
|
|
487
|
+
const counted = await sql `
|
|
488
|
+
SELECT count(*) AS n FROM capybara.conversations c
|
|
489
|
+
WHERE c.org_id = ${orgId} AND c.account_id = ${accountId} AND c.kind = 'chat'
|
|
490
|
+
AND c.deleted_at IS NULL AND c.archived_at IS NOT NULL
|
|
491
|
+
AND c.agent_id IN ${visibleAgents}
|
|
492
|
+
`.execute(this.db.db);
|
|
493
|
+
return { groups: page, next, archivedCount: Number(counted.rows[0]?.n ?? 0) };
|
|
381
494
|
}
|
|
382
495
|
/**
|
|
383
496
|
* The sub-agents of a conversation, newest first (sub-agents spec §14.3); owner only, as
|
|
@@ -544,6 +657,15 @@ let ConversationsService = class ConversationsService {
|
|
|
544
657
|
this.userMessage(accountId, input, extra, [], invoked),
|
|
545
658
|
]);
|
|
546
659
|
}
|
|
660
|
+
// The message is written: it brings an archived conversation back into the default lists
|
|
661
|
+
if (conv.archivedAt !== null) {
|
|
662
|
+
await this.db
|
|
663
|
+
.updateTable("conversations")
|
|
664
|
+
.set({ archived_at: null })
|
|
665
|
+
.where("id", "=", conversationId)
|
|
666
|
+
.execute();
|
|
667
|
+
await this.eventLog.publishChanged(conversationId, "archived");
|
|
668
|
+
}
|
|
547
669
|
await this.runs.ensureRun(conversationId);
|
|
548
670
|
return event;
|
|
549
671
|
}
|
|
@@ -551,7 +673,8 @@ let ConversationsService = class ConversationsService {
|
|
|
551
673
|
// its own transaction: the caller notifies and publishes after the commit
|
|
552
674
|
async postIn(orgId, conversationId, accountId, input, next, extra, invoked) {
|
|
553
675
|
const blocks = await this.bindIn(orgId, conversationId, accountId, input.files ?? []);
|
|
554
|
-
|
|
676
|
+
const written = await this.writeMessage(conversationId, accountId, input, next, extra, blocks, invoked);
|
|
677
|
+
return written.at(-1);
|
|
555
678
|
}
|
|
556
679
|
/**
|
|
557
680
|
* The selection a message switches to, or null when it changes nothing. A model equal to the
|
|
@@ -591,7 +714,8 @@ let ConversationsService = class ConversationsService {
|
|
|
591
714
|
}
|
|
592
715
|
/**
|
|
593
716
|
* Writes the message (preceded by its `model_switched` when it switches) inside `t` and updates
|
|
594
|
-
* `conversations.selection`; returns the
|
|
717
|
+
* `conversations.selection`; returns the events written, in seq order (the `user_message`
|
|
718
|
+
* last). The caller notifies after commit.
|
|
595
719
|
*/
|
|
596
720
|
async writeMessage(conversationId, accountId, input, next, extra, blocks, invoked) {
|
|
597
721
|
const events = [];
|
|
@@ -616,7 +740,7 @@ let ConversationsService = class ConversationsService {
|
|
|
616
740
|
.where("id", "=", conversationId)
|
|
617
741
|
.execute();
|
|
618
742
|
}
|
|
619
|
-
return written
|
|
743
|
+
return written;
|
|
620
744
|
}
|
|
621
745
|
/**
|
|
622
746
|
* Renders the message's skill invocation (phase D), or undefined when it has none. Resolved
|
|
@@ -718,6 +842,9 @@ let ConversationsService = class ConversationsService {
|
|
|
718
842
|
__decorate([
|
|
719
843
|
Transactional({ propagation: "requires_new" })
|
|
720
844
|
], ConversationsService.prototype, "createIn", null);
|
|
845
|
+
__decorate([
|
|
846
|
+
Transactional()
|
|
847
|
+
], ConversationsService.prototype, "markDeleted", null);
|
|
721
848
|
__decorate([
|
|
722
849
|
Transactional({ propagation: "requires_new" })
|
|
723
850
|
], ConversationsService.prototype, "postIn", null);
|
|
@@ -739,6 +866,7 @@ ConversationsService = __decorate([
|
|
|
739
866
|
__param(7, Inject(ConnectorContent)),
|
|
740
867
|
__param(8, Inject(AttachmentsService)),
|
|
741
868
|
__param(9, Inject(SkillInvoker)),
|
|
742
|
-
__param(10, Inject(
|
|
869
|
+
__param(10, Inject(BackgroundTaskStopper)),
|
|
870
|
+
__param(11, Inject(TransactionHost))
|
|
743
871
|
], ConversationsService);
|
|
744
872
|
export { ConversationsService };
|
|
@@ -64,7 +64,7 @@ export type ConversationBusMessage = {
|
|
|
64
64
|
* What changed about a conversation outside its event log (UI refresh B): the SSE stream sends it as
|
|
65
65
|
* `event: changed` with `{ what }`, and the web refetches that part. Never model-visible.
|
|
66
66
|
*/
|
|
67
|
-
export declare const CONVERSATION_CHANGES: readonly ["title", "queue", "selection", "files"];
|
|
67
|
+
export declare const CONVERSATION_CHANGES: readonly ["title", "queue", "selection", "files", "archived", "deleted", "background"];
|
|
68
68
|
export type ConversationChange = (typeof CONVERSATION_CHANGES)[number];
|
|
69
69
|
/**
|
|
70
70
|
* 会话事件日志:只追加,是 agent 运行的唯一事实来源。
|
|
@@ -41,7 +41,16 @@ export const conversationTopic = (conversationId) => `conv.${conversationId}`;
|
|
|
41
41
|
* What changed about a conversation outside its event log (UI refresh B): the SSE stream sends it as
|
|
42
42
|
* `event: changed` with `{ what }`, and the web refetches that part. Never model-visible.
|
|
43
43
|
*/
|
|
44
|
-
export const CONVERSATION_CHANGES = [
|
|
44
|
+
export const CONVERSATION_CHANGES = [
|
|
45
|
+
"title",
|
|
46
|
+
"queue",
|
|
47
|
+
"selection",
|
|
48
|
+
"files",
|
|
49
|
+
"archived",
|
|
50
|
+
"deleted",
|
|
51
|
+
// the conversation's background tasks (or its sub-agents')
|
|
52
|
+
"background",
|
|
53
|
+
];
|
|
45
54
|
const notFound = () => new ApiError("conversation.not_found", "Conversation not found");
|
|
46
55
|
function toEvent(r) {
|
|
47
56
|
return {
|
|
@@ -272,6 +272,11 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
272
272
|
model: z.ZodString;
|
|
273
273
|
}, z.core.$strip>;
|
|
274
274
|
triggerAccountId: z.ZodString;
|
|
275
|
+
trigger: z.ZodOptional<z.ZodEnum<{
|
|
276
|
+
background_task: "background_task";
|
|
277
|
+
message: "message";
|
|
278
|
+
}>>;
|
|
279
|
+
triggerSeq: z.ZodOptional<z.ZodNumber>;
|
|
275
280
|
}, z.core.$strip>;
|
|
276
281
|
}, z.core.$strip>, z.ZodObject<{
|
|
277
282
|
type: z.ZodLiteral<"model_call">;
|
|
@@ -406,7 +411,9 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
406
411
|
purpose: z.ZodOptional<z.ZodEnum<{
|
|
407
412
|
compaction: "compaction";
|
|
408
413
|
title: "title";
|
|
414
|
+
web_fetch: "web_fetch";
|
|
409
415
|
}>>;
|
|
416
|
+
callId: z.ZodOptional<z.ZodString>;
|
|
410
417
|
}, z.core.$strip>;
|
|
411
418
|
}, z.core.$strip>, z.ZodObject<{
|
|
412
419
|
type: z.ZodLiteral<"reminder">;
|
|
@@ -496,6 +503,13 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
496
503
|
}, z.core.$strip>, z.ZodObject<{
|
|
497
504
|
type: z.ZodLiteral<"approval_requested">;
|
|
498
505
|
payload: z.ZodObject<{
|
|
506
|
+
requestedFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
507
|
+
approverRole: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
508
|
+
admin: "admin";
|
|
509
|
+
guest: "guest";
|
|
510
|
+
member: "member";
|
|
511
|
+
owner: "owner";
|
|
512
|
+
}>>>;
|
|
499
513
|
step: z.ZodNumber;
|
|
500
514
|
callId: z.ZodString;
|
|
501
515
|
approvalId: z.ZodString;
|
|
@@ -659,6 +673,10 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
659
673
|
name: z.ZodString;
|
|
660
674
|
avatarUrl: z.ZodDefault<z.ZodNullable<z.ZodURL>>;
|
|
661
675
|
instructions: z.ZodDefault<z.ZodString>;
|
|
676
|
+
instructionFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
677
|
+
name: z.ZodString;
|
|
678
|
+
content: z.ZodString;
|
|
679
|
+
}, z.core.$strict>>>;
|
|
662
680
|
model: z.ZodObject<{
|
|
663
681
|
provider: z.ZodString;
|
|
664
682
|
model: z.ZodString;
|
|
@@ -754,6 +772,13 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
754
772
|
}, z.core.$strip>, z.ZodObject<{
|
|
755
773
|
type: z.ZodLiteral<"subagent_waiting">;
|
|
756
774
|
payload: z.ZodObject<{
|
|
775
|
+
requestedFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
776
|
+
approverRole: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
777
|
+
admin: "admin";
|
|
778
|
+
guest: "guest";
|
|
779
|
+
member: "member";
|
|
780
|
+
owner: "owner";
|
|
781
|
+
}>>>;
|
|
757
782
|
subagentId: z.ZodString;
|
|
758
783
|
childRunId: z.ZodString;
|
|
759
784
|
kind: z.ZodEnum<{
|
|
@@ -799,6 +824,29 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
799
824
|
childRunId: z.ZodOptional<z.ZodString>;
|
|
800
825
|
text: z.ZodString;
|
|
801
826
|
}, z.core.$strip>;
|
|
827
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
828
|
+
type: z.ZodLiteral<"background_task_finished">;
|
|
829
|
+
payload: z.ZodObject<{
|
|
830
|
+
taskId: z.ZodString;
|
|
831
|
+
description: z.ZodString;
|
|
832
|
+
status: z.ZodEnum<{
|
|
833
|
+
completed: "completed";
|
|
834
|
+
failed: "failed";
|
|
835
|
+
lost: "lost";
|
|
836
|
+
stopped: "stopped";
|
|
837
|
+
timed_out: "timed_out";
|
|
838
|
+
}>;
|
|
839
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
840
|
+
elapsedMs: z.ZodNumber;
|
|
841
|
+
lastLines: z.ZodString;
|
|
842
|
+
outputPath: z.ZodString;
|
|
843
|
+
reported: z.ZodBoolean;
|
|
844
|
+
lostReason: z.ZodOptional<z.ZodString>;
|
|
845
|
+
stoppedBy: z.ZodOptional<z.ZodEnum<{
|
|
846
|
+
deletion: "deletion";
|
|
847
|
+
person: "person";
|
|
848
|
+
}>>;
|
|
849
|
+
}, z.core.$strip>;
|
|
802
850
|
}, z.core.$strip>, z.ZodObject<{
|
|
803
851
|
type: z.ZodLiteral<"compaction_summary">;
|
|
804
852
|
payload: z.ZodObject<{
|
|
@@ -859,6 +907,16 @@ export type ConversationEvent = ConversationEventBody & {
|
|
|
859
907
|
runId: string | null;
|
|
860
908
|
createdAt: Date;
|
|
861
909
|
};
|
|
910
|
+
/** An event a run absorbs at a step boundary: a person's message or a finished background task. */
|
|
911
|
+
export type AbsorbableEvent = Extract<ConversationEvent, {
|
|
912
|
+
type: "user_message" | "background_task_finished";
|
|
913
|
+
}>;
|
|
914
|
+
/**
|
|
915
|
+
* Whether `e` is absorbed like an interjection (spec 2026-11-05-background-bash-design §2.4): the
|
|
916
|
+
* loop takes it at its next step boundary, `consumed_seq` moves past it, and request building
|
|
917
|
+
* places it where the model call that absorbed it was built.
|
|
918
|
+
*/
|
|
919
|
+
export declare const isAbsorbable: (e: ConversationEvent) => e is AbsorbableEvent;
|
|
862
920
|
/**
|
|
863
921
|
* What the person typed for a skill invocation (phase D): `/<plugin>:<name> <arguments>` (no
|
|
864
922
|
* plugin: `/<name>`). Titles and eval case titles use it instead of the rendered skill in
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DOCUMENT_MEDIA_TYPES, hasControlChars, IMAGE_MEDIA_TYPES, isAttachmentId, isSkillName, MEDIA_NAME_MAX_CHARS, } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { AgentDefinitionSchema, AgentToolRef, ModelRef } from "../agents/definition.js";
|
|
4
|
+
import { ORG_ROLES } from "../orgs/roles.js";
|
|
4
5
|
import { TOOL_VIEW_DIFF_MAX_LINES, TOOL_VIEW_LINE_MAX_CHARS, TOOL_VIEW_PATH_MAX_CHARS, } from "../tools/view.js";
|
|
5
6
|
/** 供应商私有数据(`ProviderOpaque`):原样保存、只回放给同一供应商。 */
|
|
6
7
|
const ProviderOpaqueSchema = z.object({
|
|
@@ -17,6 +18,15 @@ const RefMediaSourceSchema = z.object({
|
|
|
17
18
|
kind: z.literal("ref"),
|
|
18
19
|
attachmentId: AttachmentIdSchema,
|
|
19
20
|
});
|
|
21
|
+
/**
|
|
22
|
+
* Who may decide an approval, as the approval row says (display only, absent in older logs): the
|
|
23
|
+
* assigned account (`requestedFor`), else the escalation role (`approverRole`). Pages word the
|
|
24
|
+
* waiting state with it ("等待 张三 批准") for viewers who cannot decide.
|
|
25
|
+
*/
|
|
26
|
+
const ApproverFields = {
|
|
27
|
+
requestedFor: z.string().nullable().optional(),
|
|
28
|
+
approverRole: z.enum(ORG_ROLES).nullable().optional(),
|
|
29
|
+
};
|
|
20
30
|
/** The longest deny reason (characters): the decision route's limit and `approval_decided.reason`'s. */
|
|
21
31
|
export const APPROVAL_DENY_REASON_MAX_CHARS = 500;
|
|
22
32
|
/** 一条 `media_excluded` 事件最多的附件数。 */
|
|
@@ -239,6 +249,14 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
239
249
|
agentVersion: z.number().int().positive(),
|
|
240
250
|
model: ModelRef,
|
|
241
251
|
triggerAccountId: z.string(),
|
|
252
|
+
/**
|
|
253
|
+
* What started the run (display only): absent or `message` = a person's message (or a
|
|
254
|
+
* sub-agent's task), `background_task` = a finished background task woke the conversation
|
|
255
|
+
* (spec 2026-11-05-background-bash-design §3).
|
|
256
|
+
*/
|
|
257
|
+
trigger: z.enum(["message", "background_task"]).optional(),
|
|
258
|
+
/** A wake run's boundary: the seq of the finish that woke it (full-run replay seeds from it). */
|
|
259
|
+
triggerSeq: seqNo.optional(),
|
|
242
260
|
})),
|
|
243
261
|
ev("model_call", z.object({
|
|
244
262
|
step: count,
|
|
@@ -326,14 +344,18 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
326
344
|
* Usage of one model call. Absent `purpose` = the step's call (written with its
|
|
327
345
|
* `assistant_message`); `compaction` = the compaction call before the step (I-1: billed and
|
|
328
346
|
* reported, but no step consumer may pair it with the step's `model_call`); `title` = the
|
|
329
|
-
* conversation title call (UI refresh B: no run, `step` 0, not model-visible, billed and reported)
|
|
347
|
+
* conversation title call (UI refresh B: no run, `step` 0, not model-visible, billed and reported);
|
|
348
|
+
* `web_fetch` = a `WebFetch` extraction call (web tools spec §5: the tool call's run and step,
|
|
349
|
+
* `callId` = the call; not model-visible, billed and reported, counted on a sub-agent's parent).
|
|
330
350
|
*/
|
|
331
351
|
ev("usage", z.object({
|
|
332
352
|
step: count,
|
|
333
353
|
provider: z.string(),
|
|
334
354
|
model: z.string(),
|
|
335
355
|
...ModelUsageShape,
|
|
336
|
-
purpose: z.enum(["compaction", "title"]).optional(),
|
|
356
|
+
purpose: z.enum(["compaction", "title", "web_fetch"]).optional(),
|
|
357
|
+
/** The tool call a `web_fetch` usage belongs to. */
|
|
358
|
+
callId: z.string().optional(),
|
|
337
359
|
})),
|
|
338
360
|
ev("reminder", z.object({ step: count, text: z.string() })),
|
|
339
361
|
/** 工具调用开始执行之前写入(续跑时「有开始无结果」的调用记为结果未知,不再执行)。 */
|
|
@@ -392,6 +414,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
392
414
|
expiresAt: z.string(),
|
|
393
415
|
/** Which rule asked (UI refresh B; display only, absent in older logs). */
|
|
394
416
|
policy: z.enum(["agent", "connector", "default", "role", "org", "fixed"]).optional(),
|
|
417
|
+
...ApproverFields,
|
|
395
418
|
})),
|
|
396
419
|
/** 审批已处理(批准 / 拒绝 / 超时 / 取消);`scope` 只在批准时出现,`byAccountId` 为处理人(超时 / 取消为 null)。 */
|
|
397
420
|
ev("approval_decided", z.object({
|
|
@@ -575,6 +598,8 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
575
598
|
questionId: z.string().optional(),
|
|
576
599
|
toolKey: z.string(),
|
|
577
600
|
expiresAt: z.string().optional(),
|
|
601
|
+
/** An approval's approver (`kind: "approval"`), as in `approval_requested`. */
|
|
602
|
+
...ApproverFields,
|
|
578
603
|
})),
|
|
579
604
|
/** Parent log: a sub-agent's run ended. Not model-visible (its reply is the call's tool_result). */
|
|
580
605
|
ev("subagent_finished", z.object({
|
|
@@ -609,6 +634,30 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
609
634
|
childRunId: z.string().optional(),
|
|
610
635
|
text: z.string(),
|
|
611
636
|
})),
|
|
637
|
+
/**
|
|
638
|
+
* A background Bash task ended (spec 2026-11-05-background-bash-design §2). Model-visible: it is
|
|
639
|
+
* absorbed like an interjection and rendered as a system message (`renderBackgroundFinished`).
|
|
640
|
+
* `reported`: a TaskOutput or TaskStop in the same run already told the model; it is absorbed
|
|
641
|
+
* like any other, renders as one short line and never wakes a run. `lastLines` is the tail of
|
|
642
|
+
* the task's output.
|
|
643
|
+
*/
|
|
644
|
+
ev("background_task_finished", z.object({
|
|
645
|
+
taskId: z.string().min(1).max(64),
|
|
646
|
+
description: z.string(),
|
|
647
|
+
status: z.enum(["completed", "failed", "stopped", "timed_out", "lost"]),
|
|
648
|
+
exitCode: z.number().int().nullable(),
|
|
649
|
+
elapsedMs: count,
|
|
650
|
+
lastLines: z.string().max(4000),
|
|
651
|
+
outputPath: z.string(),
|
|
652
|
+
reported: z.boolean(),
|
|
653
|
+
lostReason: z.string().optional(),
|
|
654
|
+
/**
|
|
655
|
+
* The finish never wakes a run, but the next run absorbs it like any other (background
|
|
656
|
+
* Bash §3): `person`, a person stopped the task (the conversation's background section);
|
|
657
|
+
* `deletion`, the conversation was being deleted.
|
|
658
|
+
*/
|
|
659
|
+
stoppedBy: z.enum(["person", "deletion"]).optional(),
|
|
660
|
+
})),
|
|
612
661
|
ev("compaction_summary", z.object({ coversUpToSeq: seqNo, text: z.string() })),
|
|
613
662
|
ev("compaction_failed", z.object({ attempt: count, reason: z.string() })),
|
|
614
663
|
ev("run_finished", z.object({
|
|
@@ -630,6 +679,12 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
630
679
|
.optional(),
|
|
631
680
|
})),
|
|
632
681
|
]);
|
|
682
|
+
/**
|
|
683
|
+
* Whether `e` is absorbed like an interjection (spec 2026-11-05-background-bash-design §2.4): the
|
|
684
|
+
* loop takes it at its next step boundary, `consumed_seq` moves past it, and request building
|
|
685
|
+
* places it where the model call that absorbed it was built.
|
|
686
|
+
*/
|
|
687
|
+
export const isAbsorbable = (e) => e.type === "user_message" || e.type === "background_task_finished";
|
|
633
688
|
/**
|
|
634
689
|
* What the person typed for a skill invocation (phase D): `/<plugin>:<name> <arguments>` (no
|
|
635
690
|
* plugin: `/<name>`). Titles and eval case titles use it instead of the rendered skill in
|
|
@@ -125,6 +125,7 @@ export declare function queuedUserMessage(r: QueueRow): AppendEvent;
|
|
|
125
125
|
export declare function appendQueuedIn(t: KernelTrx, eventLog: Pick<EventLog, "appendIn">, conversationId: string, r: QueueRow): Promise<{
|
|
126
126
|
event: ConversationEvent;
|
|
127
127
|
switched: boolean;
|
|
128
|
+
unarchived: boolean;
|
|
128
129
|
}>;
|
|
129
130
|
/**
|
|
130
131
|
* Why a queued item's skill can no longer be invoked, or null (also null for a plain message).
|
|
@@ -145,11 +146,12 @@ export declare const queueFull: () => ApiError;
|
|
|
145
146
|
* when the queue is empty. An item whose message is already in the log (a retried enqueue after it
|
|
146
147
|
* was sent) is dropped and the next one is taken. A head item whose skill can no longer be invoked
|
|
147
148
|
* stays queued and the queue pauses (`skill_not_invocable`): `{ paused }`. The caller notifies
|
|
148
|
-
* after commit.
|
|
149
|
+
* after commit (and publishes `archived` when the send unarchived the conversation).
|
|
149
150
|
*/
|
|
150
151
|
export declare function dispatchQueuedIn(t: KernelTrx, eventLog: Pick<EventLog, "appendIn">, conversationId: string): Promise<{
|
|
151
152
|
event: ConversationEvent;
|
|
152
153
|
switched: boolean;
|
|
154
|
+
unarchived: boolean;
|
|
153
155
|
} | {
|
|
154
156
|
paused: QueuePauseReason;
|
|
155
157
|
} | null>;
|
|
@@ -129,7 +129,18 @@ export async function appendQueuedIn(t, eventLog, conversationId, r) {
|
|
|
129
129
|
.where("id", "=", conversationId)
|
|
130
130
|
.execute();
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
// As a direct send: the message brings an archived conversation back into the default lists
|
|
133
|
+
const unarchived = await t
|
|
134
|
+
.updateTable("conversations")
|
|
135
|
+
.set({ archived_at: null })
|
|
136
|
+
.where("id", "=", conversationId)
|
|
137
|
+
.where("archived_at", "is not", null)
|
|
138
|
+
.executeTakeFirst();
|
|
139
|
+
return {
|
|
140
|
+
event: written.at(-1),
|
|
141
|
+
switched: next !== null,
|
|
142
|
+
unarchived: Number(unarchived.numUpdatedRows) > 0,
|
|
143
|
+
};
|
|
133
144
|
}
|
|
134
145
|
/** The model of the conversation agent's current version (inside `t`). */
|
|
135
146
|
async function agentModel(t, conversationId) {
|
|
@@ -206,7 +217,7 @@ export const queueFull = () => new ApiError("conversation.queue_full", `A conver
|
|
|
206
217
|
* when the queue is empty. An item whose message is already in the log (a retried enqueue after it
|
|
207
218
|
* was sent) is dropped and the next one is taken. A head item whose skill can no longer be invoked
|
|
208
219
|
* stays queued and the queue pauses (`skill_not_invocable`): `{ paused }`. The caller notifies
|
|
209
|
-
* after commit.
|
|
220
|
+
* after commit (and publishes `archived` when the send unarchived the conversation).
|
|
210
221
|
*/
|
|
211
222
|
export async function dispatchQueuedIn(t, eventLog, conversationId) {
|
|
212
223
|
for (;;) {
|
|
@@ -240,11 +240,13 @@ let ConversationQueue = class ConversationQueue {
|
|
|
240
240
|
*/
|
|
241
241
|
async sendNow(orgId, conversationId, accountId, itemId) {
|
|
242
242
|
await this.conversations.writable(orgId, conversationId, accountId);
|
|
243
|
-
const { event, fresh, switched } = await this.sendNowIn(conversationId, itemId);
|
|
243
|
+
const { event, fresh, switched, unarchived } = await this.sendNowIn(conversationId, itemId);
|
|
244
244
|
if (fresh)
|
|
245
245
|
await this.eventLog.notify(conversationId, event.seq);
|
|
246
246
|
if (switched)
|
|
247
247
|
await this.eventLog.publishChanged(conversationId, "selection");
|
|
248
|
+
if (unarchived)
|
|
249
|
+
await this.eventLog.publishChanged(conversationId, "archived");
|
|
248
250
|
await this.eventLog.publishChanged(conversationId, "queue");
|
|
249
251
|
await this.runs.ensureRun(conversationId);
|
|
250
252
|
return event;
|