@dxos/assistant 0.14.0 → 0.16.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 (40) hide show
  1. package/dist/lib/ExecutionGraph.mjs.map +1 -1
  2. package/dist/lib/chunk-Agent.mjs +202 -0
  3. package/dist/lib/chunk-Agent.mjs.map +1 -0
  4. package/dist/lib/chunk-AiContext.mjs +462 -0
  5. package/dist/lib/chunk-AiContext.mjs.map +1 -0
  6. package/dist/lib/chunk-AiRequest.mjs.map +1 -1
  7. package/dist/lib/chunk-AiSession.mjs +2 -2
  8. package/dist/lib/chunk-AiSession.mjs.map +1 -1
  9. package/dist/lib/chunk-Alarm.mjs +37 -0
  10. package/dist/lib/chunk-Alarm.mjs.map +1 -0
  11. package/dist/lib/chunk-Chat.mjs +258 -0
  12. package/dist/lib/chunk-Chat.mjs.map +1 -0
  13. package/dist/lib/chunk-Harness.mjs +4 -4
  14. package/dist/lib/chunk-Harness.mjs.map +1 -1
  15. package/dist/lib/chunk-SessionLink.mjs +20 -0
  16. package/dist/lib/chunk-SessionLink.mjs.map +1 -0
  17. package/dist/lib/chunk-SkillHooks.mjs +47 -0
  18. package/dist/lib/chunk-SkillHooks.mjs.map +1 -0
  19. package/dist/lib/extraction.mjs.map +1 -1
  20. package/dist/lib/index.mjs +6 -6
  21. package/dist/lib/index.mjs.map +1 -1
  22. package/dist/lib/request/AiRequest.mjs +1 -2
  23. package/dist/lib/session/AiContext.mjs +1 -462
  24. package/dist/lib/session/AiSession.mjs +1 -2
  25. package/dist/lib/session/Alarm.mjs +1 -37
  26. package/dist/lib/session/Harness.mjs +1 -2
  27. package/dist/lib/session/SessionLink.mjs +1 -20
  28. package/dist/lib/session/SkillHooks.mjs +1 -47
  29. package/dist/lib/types/Agent.mjs +1 -202
  30. package/dist/lib/types/Chat.mjs +1 -258
  31. package/dist/types/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +23 -23
  33. package/src/request/AiRequest.test.ts +1 -1
  34. package/src/types/Agent.ts +1 -1
  35. package/dist/lib/session/AiContext.mjs.map +0 -1
  36. package/dist/lib/session/Alarm.mjs.map +0 -1
  37. package/dist/lib/session/SessionLink.mjs.map +0 -1
  38. package/dist/lib/session/SkillHooks.mjs.map +0 -1
  39. package/dist/lib/types/Agent.mjs.map +0 -1
  40. package/dist/lib/types/Chat.mjs.map +0 -1
@@ -1,47 +1 @@
1
- import { t as __exportAll } from "../chunk-rolldown-runtime.mjs";
2
- import * as Effect from "effect/Effect";
3
- import * as Operation from "@dxos/compute/Operation";
4
- import { Database, Obj } from "@dxos/echo";
5
- import { invariant } from "@dxos/invariant";
6
- import { log } from "@dxos/log";
7
- //#region src/session/SkillHooks.ts
8
- var SkillHooks_exports = /* @__PURE__ */ __exportAll({ runHooks: () => runHooks });
9
- var __dxlog_file = "/__w/dxos/dxos/packages/core/compute/assistant/src/session/SkillHooks.ts";
10
- /**
11
- * Runs every {@link Skill.Hook} on `skills` whose spec matches `phase`. Each hook resolves its
12
- * `function` ref to a {@link Operation.PersistentOperation}, deserializes it to a definition, and
13
- * invokes it with the templated `input` via `invoke`. Hooks without a function ref are skipped.
14
- *
15
- * Hook failures are isolated: a throwing hook logs and is swallowed so one skill's hook cannot
16
- * abort the request lifecycle (or the sibling hooks).
17
- */
18
- var runHooks = ({ skills, phase, invoke }) => Effect.gen(function* () {
19
- for (const skill of skills) for (const hook of skill.hooks ?? []) {
20
- if (hook.spec._tag !== phase) continue;
21
- if (!hook.function) continue;
22
- yield* Effect.gen(function* () {
23
- const record = yield* Database.load(hook.function);
24
- invariant(Obj.instanceOf(Operation.PersistentOperation, record), "Hook function must be an operation.", {
25
- "~LogMeta": "~LogMeta",
26
- F: __dxlog_file,
27
- L: 61,
28
- S: this,
29
- A: ["Obj.instanceOf(Operation.PersistentOperation, record)", "'Hook function must be an operation.'"]
30
- });
31
- yield* invoke(Operation.deserialize(record), { ...hook.input }, hook);
32
- }).pipe(Effect.catchCause((cause) => Effect.sync(() => log.warn("skill hook failed", {
33
- phase,
34
- skill: Obj.getMeta(skill).key,
35
- cause
36
- }, {
37
- "~LogMeta": "~LogMeta",
38
- F: __dxlog_file,
39
- L: 70,
40
- S: this
41
- }))));
42
- }
43
- });
44
- //#endregion
45
- export { runHooks, SkillHooks_exports as t };
46
-
47
- //# sourceMappingURL=SkillHooks.mjs.map
1
+ export { n as runHooks } from "../chunk-SkillHooks.mjs";
@@ -1,202 +1 @@
1
- import { t as __exportAll } from "../chunk-rolldown-runtime.mjs";
2
- import { r as HarnessContextError } from "../chunk-errors.mjs";
3
- import { Chat, linkCompanion, make } from "./Chat.mjs";
4
- import * as Effect from "effect/Effect";
5
- import * as Schema from "effect/Schema";
6
- import { Annotation, DXN, Database, Feed, Filter, Obj, Ref, Type } from "@dxos/echo";
7
- import { IdentityDid } from "@dxos/keys";
8
- import * as Skill from "@dxos/compute/Skill";
9
- import * as EffectEx from "@dxos/effect/EffectEx";
10
- import * as Instructions from "@dxos/compute/Instructions";
11
- //#region src/types/Agent.ts
12
- var Agent_exports = /* @__PURE__ */ __exportAll({
13
- Agent: () => Agent,
14
- chatLocation: () => chatLocation,
15
- getFromChatContext: () => getFromChatContext,
16
- isAgentChat: () => isAgentChat,
17
- loadChat: () => loadChat,
18
- loadForChat: () => loadForChat,
19
- loadInstructions: () => loadInstructions,
20
- makeInitialized: () => makeInitialized,
21
- resetChatHistory: () => resetChatHistory
22
- });
23
- /**
24
- * An agent identity: a personality (attribution DID) plus its preset payload (instructions with
25
- * text, skills, objects, and commands). Owns no conversation state — a chat is linked to the agent
26
- * it runs as by the ECHO parent edge (the agent parents its chats), and durable work products live on a Project
27
- * (plugin-projects DESIGN.md, "Agent ↔ Project convergence").
28
- */
29
- /**
30
- * The AI session runtime (MCP SDK, Anthropic client, ~280 KB) is loaded lazily: this module holds
31
- * the Agent *schema*, which core plugins reference for operation definitions, so the runtime loads
32
- * only when an agent operation actually runs.
33
- */
34
- var aiContextRuntime = () => import("../session/AiContext.mjs");
35
- var harnessRuntime = () => import("../session/Harness.mjs");
36
- var Agent = class extends Type.makeObject(DXN.make("org.dxos.type.agent", "0.2.0"))(Schema.Struct({
37
- name: Schema.optional(Schema.String),
38
- /**
39
- * Stable identity DID for the agent, used to attribute content it authors (e.g. suggestion
40
- * branches) as its own author — distinct from any human or other agent. Provisioned
41
- * externally, never minted by {@link makeInitialized} (see the note there); the slot a real
42
- * HALO identity DID takes once agents get first-class identities. Optional because nothing
43
- * populates it yet.
44
- */
45
- did: Schema.optional(IdentityDid.annotate({
46
- title: "DID",
47
- description: "The agent's identity DID; attributes content the agent authors."
48
- })).annotate({
49
- title: "DID",
50
- description: "The agent's DID."
51
- }),
52
- /**
53
- * Master switch for the agent's automation (propagated onto its compiled routine triggers).
54
- */
55
- enabled: Schema.optional(Schema.Boolean).annotate({
56
- title: "Enabled",
57
- description: "Master switch for agent automation; propagated to all triggers on sync."
58
- }),
59
- /**
60
- * Instructions for the agent — the preset payload (text, skills, objects, commands) a chat
61
- * receives when the agent is applied to it. Owned: `SetParent` cascades it with the agent.
62
- */
63
- instructions: Ref.Ref(Instructions.Instructions).pipe(Annotation.SetParent.set(), Annotation.FormInlineAnnotation.set(true), Schema.annotate({ title: "Instructions" }))
64
- }).pipe(Annotation.LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
65
- icon: "ph--drone--regular",
66
- hue: "amber"
67
- }), Annotation.UserType.set())) {};
68
- /**
69
- * Resolves the agent's instructions and their markdown text.
70
- */
71
- var loadInstructions = (agent) => Effect.gen(function* () {
72
- const instructions = yield* Database.load(agent.instructions);
73
- return {
74
- text: yield* Database.load(instructions.text).pipe(Effect.map((doc) => doc.content), Effect.catchTag("EntityNotFoundError", () => Effect.succeed(""))),
75
- instructions
76
- };
77
- });
78
- /**
79
- * Resolves the agent's primary chat (the agent holds no chat ref): the latest chat parented to the
80
- * agent. Entity ids are ULIDs, so the id order is creation order — `resetChatHistory` makes the
81
- * replacement chat primary simply by creating it later.
82
- */
83
- var loadChat = (agent) => Effect.gen(function* () {
84
- return (yield* Database.query(Filter.and(Filter.type(Chat), Filter.childOf(agent))).run).filter((chat) => Obj.getMeta(chat).keys.length === 0).sort((left, right) => left.id.localeCompare(right.id)).at(-1);
85
- }).pipe(Effect.orDie);
86
- /**
87
- * Resolves the agent a chat runs as, if any. Plain (agentless) chats yield `undefined`.
88
- * The parent is loaded with the object, so this is synchronous — no query.
89
- */
90
- var loadForChat = (chat) => Effect.sync(() => {
91
- const parent = Obj.getParent(chat);
92
- return parent && Obj.instanceOf(Agent, parent) ? parent : void 0;
93
- });
94
- /**
95
- * Where a chat's turns run: as its `remote` flag says, else on EDGE for an agent's chat, since its chats share
96
- * the agent's one brain, which only EDGE holds. An explicit `remote: false` keeps an agent's chats local, for
97
- * a runtime with no EDGE to reach (a story or test), where every chat shares the client's brain instead.
98
- */
99
- var chatLocation = (chat) => chat.remote ?? isAgentChat(chat) ? "edge" : "local";
100
- /** Whether the chat runs as an agent (the agent parents it); synchronous, like {@link loadForChat}. */
101
- var isAgentChat = (chat) => {
102
- const parent = Obj.getParent(chat);
103
- return parent !== void 0 && Obj.instanceOf(Agent, parent);
104
- };
105
- /**
106
- * Creates a fully initialized Agent with its first chat and context bindings.
107
- *
108
- * @param props - Agent properties including spec, skills, and context objects.
109
- * @param skill - The skill to use for the agent context: a skill object is cloned into the space, a ref
110
- * (e.g. a registry URI) is bound as-is so the agent follows the compiled skill until customized.
111
- * @returns An Effect that yields the initialized Agent.
112
- */
113
- var makeInitialized = (props, skill) => Effect.gen(function* () {
114
- const { skills: propsSkills, contextObjects, ...agentProps } = props;
115
- const AiContextRuntime = yield* Effect.promise(aiContextRuntime);
116
- const persistedPropsSkills = yield* Effect.all((propsSkills ?? []).map((ref) => {
117
- const target = ref.peek();
118
- return target !== void 0 ? Database.add(target).pipe(Effect.map((persisted) => Ref.make(persisted))) : Effect.succeed(ref);
119
- }));
120
- const instructions = yield* Database.add(Instructions.make({
121
- text: props.instructions,
122
- skills: persistedPropsSkills
123
- }));
124
- const agent = yield* Database.add(Obj.make(Agent, {
125
- ...agentProps,
126
- instructions: Ref.make(instructions),
127
- enabled: props.enabled ?? true
128
- }));
129
- const feed = yield* Database.add(Feed.make());
130
- const runtime = yield* Effect.context();
131
- const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({
132
- feed,
133
- runtime
134
- }));
135
- const agentSkill = Ref.isRef(skill) ? skill : Ref.make(yield* Database.add(Obj.clone(skill, { deep: "all" })));
136
- const chat = yield* Database.add(make({
137
- feed: Ref.make(feed),
138
- instructions: Ref.make(instructions)
139
- }));
140
- linkCompanion({
141
- chat,
142
- subject: agent
143
- });
144
- yield* Effect.promise(() => contextBinder.bind({
145
- skills: [agentSkill, ...persistedPropsSkills],
146
- objects: [
147
- Ref.make(agent),
148
- Ref.make(chat),
149
- ...contextObjects ?? []
150
- ]
151
- }));
152
- return agent;
153
- }).pipe(Effect.scoped);
154
- /**
155
- * Resets the agent's chat history by rebuilding the chat context.
156
- * Preserves the existing skills and objects from the current chat context.
157
- *
158
- * @param agent - The agent whose chat history should be reset. Must have an existing chat.
159
- * @returns An Effect that resets the chat history.
160
- */
161
- var resetChatHistory = (agent) => Effect.gen(function* () {
162
- const existingChat = yield* loadChat(agent);
163
- if (!existingChat) return yield* Effect.die(/* @__PURE__ */ new Error("Agent must have an existing chat to reset."));
164
- const existingFeed = yield* Database.load(existingChat.feed);
165
- const runtime = yield* Effect.context();
166
- const AiContextRuntime = yield* Effect.promise(aiContextRuntime);
167
- const existingContextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({
168
- feed: existingFeed,
169
- runtime
170
- }));
171
- const skills = existingContextBinder.getSkills().map(Skill.makeRef);
172
- const objects = existingContextBinder.getObjects().filter((object) => !Obj.instanceOf(Chat, object)).map((object) => Ref.make(object));
173
- const feed = yield* Database.add(Feed.make());
174
- const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({
175
- feed,
176
- runtime
177
- }));
178
- const chat = yield* Database.add(make({
179
- feed: Ref.make(feed),
180
- instructions: agent.instructions
181
- }));
182
- linkCompanion({
183
- chat,
184
- subject: agent
185
- });
186
- yield* Effect.promise(() => contextBinder.bind({
187
- skills,
188
- objects: [...objects, Ref.make(chat)]
189
- }));
190
- }).pipe(Effect.scoped);
191
- var getFromChatContext = Effect.gen(function* () {
192
- const agents = yield* (yield* Effect.promise(harnessRuntime)).queryContext(Filter.type(Agent));
193
- if (agents.length !== 1) return yield* Effect.fail(new HarnessContextError({
194
- type: "agent",
195
- count: agents.length
196
- }));
197
- return agents[0];
198
- });
199
- //#endregion
200
- export { Agent, chatLocation, getFromChatContext, isAgentChat, loadChat, loadForChat, loadInstructions, makeInitialized, resetChatHistory, Agent_exports as t };
201
-
202
- //# sourceMappingURL=Agent.mjs.map
1
+ export { t as Agent, r as chatLocation, i as getFromChatContext, a as isAgentChat, o as loadChat, s as loadForChat, c as loadInstructions, l as makeInitialized, u as resetChatHistory } from "../chunk-Agent.mjs";
@@ -1,258 +1 @@
1
- import { t as __exportAll } from "../chunk-rolldown-runtime.mjs";
2
- import * as Effect from "effect/Effect";
3
- import * as Option from "effect/Option";
4
- import * as Schema from "effect/Schema";
5
- import { SessionConfig } from "@dxos/ai";
6
- import { Annotation, DXN, Database, Feed, Filter, Obj, Ref, Type } from "@dxos/echo";
7
- import { log } from "@dxos/log";
8
- import { Task } from "@dxos/types";
9
- import * as Cause from "effect/Cause";
10
- import * as Instructions from "@dxos/compute/Instructions";
11
- import * as Project from "@dxos/compute/Project";
12
- import { ArchivableAnnotation } from "@dxos/schema";
13
- //#region src/types/Chat.ts
14
- var Chat_exports = /* @__PURE__ */ __exportAll({
15
- Chat: () => Chat,
16
- CompanionChatAnnotation: () => CompanionChatAnnotation,
17
- addTask: () => addTask,
18
- assignTasks: () => assignTasks,
19
- deleteTask: () => deleteTask,
20
- feedEntityId: () => feedEntityId,
21
- fields: () => fields,
22
- formatChecklist: () => formatChecklist,
23
- isOpenTask: () => isOpenTask,
24
- linkCompanion: () => linkCompanion,
25
- loadForFeed: () => loadForFeed,
26
- loadTasks: () => loadTasks,
27
- make: () => make,
28
- peekProject: () => peekProject,
29
- resolveTasks: () => resolveTasks,
30
- seedSession: () => seedSession,
31
- unassignTasks: () => unassignTasks
32
- });
33
- var __dxlog_file = "/__w/dxos/dxos/packages/core/compute/assistant/src/types/Chat.ts";
34
- /**
35
- * AI chat session.
36
- */
37
- var Chat = class extends Type.makeObject(DXN.make("org.dxos.type.assistant.chat", "0.2.0"))(Schema.Struct({
38
- name: Schema.String.pipe(Schema.optional),
39
- viewType: Schema.String.pipe(Schema.optional),
40
- /**
41
- * Runs this conversation's agent on the edge rather than locally, mirroring a trigger's own
42
- * `remote` flag. When unset, the agent runs locally on the client.
43
- */
44
- remote: Schema.Boolean.pipe(Schema.annotate({ title: "Remote" }), Schema.optional),
45
- /** Message feed, owned by the chat so `SetParent` cascades it. */
46
- feed: Ref.Ref(Feed.Feed).pipe(Annotation.SetParent.set(), Annotation.FormInputAnnotation.set(false)),
47
- /**
48
- * Instructions steering this conversation, rendered into the system prompt at request time.
49
- * Held by reference (never copied), so a project's chats follow edits to its instructions.
50
- */
51
- instructions: Schema.optional(Ref.Ref(Instructions.Instructions).pipe(Annotation.FormInputAnnotation.set(false))),
52
- /**
53
- * How this conversation runs (its model), chosen in the chat rather than globally so it survives
54
- * a remount and travels with the chat. Unset fields fall back to the project's, then the agent's.
55
- */
56
- session: Schema.optional(SessionConfig.SessionConfig.pipe(Annotation.FormInputAnnotation.set(false))),
57
- /**
58
- * The working checklist, flat and ordered. Deliberately NOT an owning (`SetParent`) field: a
59
- * chat may work on a task that belongs somewhere else — a project's task set delegates one here
60
- * — and an owning field re-parents every resolved member on each update of the chat, which
61
- * would silently move that task out of the set that owns it. Tasks the chat itself creates are
62
- * parented to it explicitly; see {@link addTask}.
63
- */
64
- tasks: Schema.Array(Ref.Ref(Task.Task)).pipe(Annotation.FormInputAnnotation.set(false))
65
- }).pipe(Annotation.LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
66
- icon: "ph--sparkle--regular",
67
- hue: "amber"
68
- }), Annotation.UserType.set(), ArchivableAnnotation.set(true))) {};
69
- /** Module-level alias so callers importing the namespace avoid the doubled `Chat.Chat.fields`. */
70
- var fields = Chat.fields;
71
- var make = (props) => Obj.make(Chat, {
72
- ...props,
73
- tasks: props.tasks ?? []
74
- });
75
- /**
76
- * Refs to the chats accompanying an object, stored on the subject itself (replaces the former
77
- * `CompanionTo` relation): the annotation is deleted with the subject, and its refs make the
78
- * parented chats real children — referenced and cascaded — rather than ref-less parent-edge
79
- * orphans reachable only by index query.
80
- */
81
- var CompanionChatAnnotation = Annotation.make({
82
- id: "org.dxos.assistant.companionChats",
83
- schema: Schema.Array(Ref.Ref(Chat))
84
- });
85
- /**
86
- * Links a chat to the subject it accompanies (a Project, an Agent, a document, ...): a ref on the
87
- * subject via {@link CompanionChatAnnotation} plus the ECHO parent edge. Idempotent per chat.
88
- */
89
- var linkCompanion = ({ chat, subject }) => {
90
- const existing = Annotation.get(subject, CompanionChatAnnotation);
91
- if (!existing.pipe(Option.getOrElse(() => [])).some((ref) => ref.uri === Ref.make(chat).uri)) {
92
- if (Option.isNone(existing)) Obj.update(subject, (subject) => {
93
- Annotation.set(subject, CompanionChatAnnotation, [Ref.make(chat)]);
94
- });
95
- else Annotation.update(subject, CompanionChatAnnotation, (chats) => {
96
- chats.push(Ref.make(chat));
97
- });
98
- }
99
- Obj.setParent(chat, subject);
100
- };
101
- /**
102
- * Starts a chat on a default session config — a project's, say — copied rather than referenced, so
103
- * a model later picked in the chat is the chat's own. A chat already carrying a config keeps it.
104
- */
105
- var seedSession = (chat, session) => {
106
- if (!session || chat.session) return;
107
- Obj.update(chat, (chat) => {
108
- chat.session = { ...session };
109
- });
110
- };
111
- /** Creates a task the chat owns and appends it to the checklist. */
112
- var addTask = (db, chat, title, props = {}) => {
113
- const task = db.add(Task.make({
114
- title: title.trim(),
115
- status: "todo",
116
- ...props,
117
- [Obj.Parent]: chat
118
- }));
119
- Obj.update(chat, (chat) => {
120
- chat.tasks.push(Ref.make(task));
121
- });
122
- return task;
123
- };
124
- /**
125
- * Appends existing tasks to the checklist. Ownership is deliberately untouched — the task keeps the
126
- * parent it arrived with (contrast {@link addTask}, which parents what the chat creates), so a task
127
- * borrowed from a project's set stays in that set. Refs already on the checklist are skipped.
128
- * Returns the refs actually appended.
129
- */
130
- var assignTasks = (chat, tasks) => {
131
- const added = [];
132
- Obj.update(chat, (chat) => {
133
- const present = new Set(chat.tasks.map((ref) => Task.refEntityId(ref)).filter((id) => id !== void 0));
134
- for (const ref of tasks) {
135
- const id = Task.refEntityId(ref);
136
- if (id === void 0 || present.has(id)) continue;
137
- present.add(id);
138
- added.push(ref);
139
- }
140
- chat.tasks.push(...added);
141
- });
142
- return added;
143
- };
144
- /**
145
- * Takes tasks off the checklist without destroying them — the inverse of {@link assignTasks}, and
146
- * distinct from {@link deleteTask}, which also removes the members the chat owns. Returns the refs
147
- * actually removed.
148
- */
149
- var unassignTasks = (chat, tasks) => {
150
- const ids = new Set(tasks.map((ref) => Task.refEntityId(ref)).filter((id) => id !== void 0));
151
- const removed = chat.tasks.filter((ref) => {
152
- const id = Task.refEntityId(ref);
153
- return id !== void 0 && ids.has(id);
154
- });
155
- if (removed.length > 0) Obj.update(chat, (chat) => {
156
- chat.tasks = chat.tasks.filter((ref) => {
157
- const id = Task.refEntityId(ref);
158
- return id === void 0 || !ids.has(id);
159
- });
160
- });
161
- return [...removed];
162
- };
163
- /**
164
- * Remove a task and its sub-tasks from `tasks`, the chat's checklist loaded in full (see
165
- * {@link loadTasks}) — an unloaded child is invisible to the walk and would be left orphaned in
166
- * the array. Returns everything dropped from the checklist.
167
- *
168
- * Only members the chat owns are destroyed: a delegated task belongs to the set that parents it,
169
- * so taking it off this checklist must not delete it from there.
170
- */
171
- var deleteTask = (db, chat, tasks, task) => {
172
- const subtree = Task.subtree(tasks, task);
173
- const ids = new Set(subtree.map((member) => member.id));
174
- Obj.update(chat, (chat) => {
175
- chat.tasks = chat.tasks.filter((ref) => {
176
- const id = Task.refEntityId(ref);
177
- return id === void 0 || !ids.has(id);
178
- });
179
- });
180
- for (const member of subtree) if (Obj.getParent(member)?.id === chat.id) db.remove(member);
181
- return subtree;
182
- };
183
- /** The chat's feed entity id, readable without resolving the ref. */
184
- var feedEntityId = (chat) => Task.refEntityId(chat.feed);
185
- /**
186
- * The chat that owns `feed`. The chat owns its feed (`SetParent`), so the parent edge answers this
187
- * without a query; the scan is the fallback for a feed whose parent was never stamped.
188
- */
189
- var loadForFeed = (feed) => Effect.gen(function* () {
190
- const parent = Obj.getParent(feed);
191
- if (parent && Obj.instanceOf(Chat, parent)) return parent;
192
- return (yield* Database.query(Filter.type(Chat)).run).find((chat) => feedEntityId(chat) === feed.id);
193
- }).pipe(Effect.catchCause((cause) => Effect.sync(() => {
194
- log.warn("chat lookup failed; treating feed as chat-less", {
195
- feed: feed.id,
196
- cause: Cause.pretty(cause)
197
- }, {
198
- "~LogMeta": "~LogMeta",
199
- F: __dxlog_file,
200
- L: 238,
201
- S: void 0
202
- });
203
- })));
204
- /** Bound on the parent walk below; a conversation sits one or two edges under its project. */
205
- var MAX_OWNER_DEPTH = 8;
206
- /**
207
- * The project a conversation belongs to, if any.
208
- *
209
- * Walks the parent chain rather than reading the immediate parent, because a chat reaches its project
210
- * through whatever owns it — directly for a project chat, through the agent for an agent's chat.
211
- */
212
- var peekProject = (chat) => {
213
- let owner = Obj.getParent(chat);
214
- for (let depth = 0; owner && depth < MAX_OWNER_DEPTH; depth++) {
215
- if (Obj.instanceOf(Project.Project, owner)) return owner;
216
- owner = Obj.getParent(owner);
217
- }
218
- };
219
- /** Loads the checklist in order, de-duplicated: a concurrent merge can land the same ref twice. */
220
- var loadTasks = (chat) => Effect.gen(function* () {
221
- const tasks = yield* Effect.forEach(chat.tasks, (task) => Database.load(task).pipe(Effect.orElseSucceed(() => void 0)));
222
- return Task.dedupeById(tasks);
223
- });
224
- /** Sync twin of {@link loadTasks}; an unresolved ref contributes nothing rather than throwing. */
225
- var resolveTasks = (chat) => Task.dedupeById(chat.tasks.filter((ref) => ref.isAvailable).map((ref) => ref.target));
226
- /** A task is open until it reaches a terminal status. */
227
- var isOpenTask = (task) => (task.status ?? "todo") === "todo" || task.status === "started";
228
- /**
229
- * The conversation's tasks rendered as a numbered checklist (the format the planning prompts
230
- * speak), or a placeholder when none exist. Ordinals match the task list UI, and non-default
231
- * status/dependencies are noted so the model can reason about readiness.
232
- */
233
- var formatChecklist = (chat) => Effect.gen(function* () {
234
- const tasks = yield* loadTasks(chat);
235
- if (tasks.length === 0) return "No checklist found.";
236
- return formatTasks(tasks);
237
- });
238
- /**
239
- * Renders tasks as `1. [ ] Title` lines, ordinals in checklist order, each followed by an indented
240
- * note line carrying the task's ref and any status/dependency notes — appended to the title, models
241
- * paste the notes back into it. The ref renders as `[MNEMONIC](uri)` so the mnemonic the user reads
242
- * and the handle the tools take travel together and get pasted back verbatim.
243
- */
244
- var formatTasks = (tasks) => {
245
- const ordinals = new Map(tasks.map((task, index) => [task.id, index + 1]));
246
- return tasks.map((task, index) => {
247
- const line = `${index + 1}. [${task.status === "done" ? "x" : " "}] ${task.title}`;
248
- const notes = [`ref: [${Obj.getMnemonic(task)}](${Obj.getURI(task)})`];
249
- if (task.status && task.status !== "todo" && task.status !== "done") notes.push(task.status);
250
- const deps = (task.dependsOn ?? []).map((ref) => ref.target).filter((target) => target !== void 0).map((target) => ordinals.get(target.id) ?? target.title);
251
- if (deps.length > 0) notes.push(`depends on ${deps.join(", ")}`);
252
- return `${line}\n (${notes.join("; ")})`;
253
- }).join("\n");
254
- };
255
- //#endregion
256
- export { Chat, CompanionChatAnnotation, addTask, assignTasks, deleteTask, feedEntityId, fields, formatChecklist, isOpenTask, linkCompanion, loadForFeed, loadTasks, make, peekProject, resolveTasks, seedSession, Chat_exports as t, unassignTasks };
257
-
258
- //# sourceMappingURL=Chat.mjs.map
1
+ export { t as Chat, r as CompanionChatAnnotation, i as addTask, a as assignTasks, o as deleteTask, s as feedEntityId, c as fields, l as formatChecklist, u as isOpenTask, d as linkCompanion, f as loadForFeed, p as loadTasks, m as make, h as peekProject, g as resolveTasks, _ as seedSession, v as unassignTasks } from "../chunk-Chat.mjs";