@danypops/papyrus 0.6.0 → 0.8.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.
package/src/ops.ts CHANGED
@@ -6,7 +6,7 @@ import { createRequire } from "node:module";
6
6
  import { exec } from "node:child_process";
7
7
  import type { Db } from "./db.ts";
8
8
  import { inTransaction } from "./db.ts";
9
- import type { Artifact, CreateArtifactInput } from "./domain/artifact.ts";
9
+ import type { Artifact, CreateArtifactInput, UpdateArtifactInput } from "./domain/artifact.ts";
10
10
  import type { Gate, GateResult, GateRunOptions } from "./domain/gate.ts";
11
11
  export type { Artifact } from "./domain/artifact.ts";
12
12
  export type { Gate, GateResult } from "./domain/gate.ts";
@@ -204,6 +204,22 @@ export function linkArtifacts(db: Db, fromId: string, relation: string, toId: st
204
204
  });
205
205
  }
206
206
 
207
+ export function updateArtifactContent(db: Db, id: string, input: UpdateArtifactInput): Artifact | null {
208
+ const artifact = getArtifact(db, id);
209
+ if (!artifact) return null;
210
+ const now = new Date().toISOString();
211
+ inTransaction(db, () => {
212
+ db.prepare("UPDATE artifacts SET title = ?, body = ?, labels = ?, updated_at = ? WHERE id = ?").run(
213
+ input.title ?? artifact.title,
214
+ input.body ?? artifact.body,
215
+ JSON.stringify(input.labels ?? artifact.labels),
216
+ now,
217
+ id,
218
+ );
219
+ });
220
+ return getArtifact(db, id);
221
+ }
222
+
207
223
  export function updateStatus(db: Db, id: string, status: string): Artifact | null {
208
224
  const art = getArtifact(db, id);
209
225
  if (!art) return null;
@@ -6,6 +6,7 @@ import type {
6
6
  ArtifactQuery,
7
7
  CreateArtifactInput,
8
8
  RelationshipQuery,
9
+ UpdateArtifactInput,
9
10
  } from "../domain/artifact.ts";
10
11
 
11
12
  export interface ArtifactStore {
@@ -15,5 +16,6 @@ export interface ArtifactStore {
15
16
  link(link: ArtifactLink): void;
16
17
  setStatus(id: string, status: string): Artifact | null;
17
18
  setExtra(id: string, extra: Record<string, unknown>): Artifact | null;
19
+ updateContent(id: string, input: UpdateArtifactInput): Artifact | null;
18
20
  relationships(filter?: RelationshipQuery): ArtifactEdge[];
19
21
  }
@@ -1,21 +1,43 @@
1
+ export type TaskFocusStatus = "active" | "paused";
2
+
3
+ export interface TaskFocusState {
4
+ taskId: string;
5
+ status: TaskFocusStatus;
6
+ updatedAt: string;
7
+ pauseReason?: string;
8
+ }
9
+
1
10
  export interface TaskFocusStore {
2
- get(): string | undefined;
3
- set(taskId: string): void;
11
+ get(): TaskFocusState | undefined;
12
+ set(taskId: string): TaskFocusState;
13
+ pause(taskId: string, reason?: string): TaskFocusState;
14
+ unpause(taskId: string): TaskFocusState;
4
15
  clear(taskId?: string): void;
5
16
  }
6
17
 
7
18
  export class InMemoryTaskFocusStore implements TaskFocusStore {
8
- private taskId: string | undefined;
19
+ private state: TaskFocusState | undefined;
20
+
21
+ get(): TaskFocusState | undefined { return this.state; }
22
+
23
+ set(taskId: string): TaskFocusState {
24
+ this.state = { taskId, status: "active", updatedAt: new Date().toISOString() };
25
+ return this.state;
26
+ }
9
27
 
10
- get(): string | undefined {
11
- return this.taskId;
28
+ pause(taskId: string, reason?: string): TaskFocusState {
29
+ if (this.state?.taskId !== taskId) throw new Error(`task "${taskId}" is not focused`);
30
+ this.state = { ...this.state, status: "paused", updatedAt: new Date().toISOString(), ...(reason ? { pauseReason: reason } : {}) };
31
+ return this.state;
12
32
  }
13
33
 
14
- set(taskId: string): void {
15
- this.taskId = taskId;
34
+ unpause(taskId: string): TaskFocusState {
35
+ if (this.state?.taskId !== taskId) throw new Error(`task "${taskId}" is not focused`);
36
+ this.state = { taskId, status: "active", updatedAt: new Date().toISOString() };
37
+ return this.state;
16
38
  }
17
39
 
18
40
  clear(taskId?: string): void {
19
- if (taskId === undefined || taskId === this.taskId) this.taskId = undefined;
41
+ if (taskId === undefined || this.state?.taskId === taskId) this.state = undefined;
20
42
  }
21
43
  }
@@ -0,0 +1,40 @@
1
+ import type { TaskProjectScope, TaskScopeSource, TaskViewMode, TaskViewPreference } from "../domain/task-scope.ts";
2
+
3
+ export interface TaskScopeStore {
4
+ assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope;
5
+ get(taskId: string): TaskProjectScope | undefined;
6
+ taskIds(projectRoot: string | undefined, limit: number): string[];
7
+ view(projectRoot: string): TaskViewPreference;
8
+ setView(projectRoot: string, mode: TaskViewMode, rootTaskId?: string): TaskViewPreference;
9
+ }
10
+
11
+ export class InMemoryTaskScopeStore implements TaskScopeStore {
12
+ private readonly scopes = new Map<string, TaskProjectScope>();
13
+ private readonly views = new Map<string, TaskViewPreference>();
14
+
15
+ assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope {
16
+ const scope = { taskId, ...(projectRoot === undefined ? {} : { projectRoot }), source };
17
+ this.scopes.set(taskId, scope);
18
+ return scope;
19
+ }
20
+
21
+ get(taskId: string): TaskProjectScope | undefined { return this.scopes.get(taskId); }
22
+
23
+ taskIds(projectRoot: string | undefined, limit: number): string[] {
24
+ return [...this.scopes.values()]
25
+ .filter((scope) => scope.projectRoot === projectRoot)
26
+ .map((scope) => scope.taskId)
27
+ .sort()
28
+ .slice(0, limit);
29
+ }
30
+
31
+ view(projectRoot: string): TaskViewPreference {
32
+ return this.views.get(projectRoot) ?? { projectRoot, mode: "project" };
33
+ }
34
+
35
+ setView(projectRoot: string, mode: TaskViewMode, rootTaskId?: string): TaskViewPreference {
36
+ const view = { projectRoot, mode, ...(rootTaskId === undefined ? {} : { rootTaskId }) };
37
+ this.views.set(projectRoot, view);
38
+ return view;
39
+ }
40
+ }
package/src/service.ts CHANGED
@@ -5,15 +5,17 @@ import { SQLiteArtifactStore } from "./adapters/sqlite-artifact-store.ts";
5
5
  import { SQLiteGateRunner } from "./adapters/sqlite-gate-runner.ts";
6
6
  import { SQLiteTaskFocusStore } from "./adapters/sqlite-task-focus-store.ts";
7
7
  import { SQLiteTaskEventStore } from "./adapters/sqlite-task-event-store.ts";
8
+ import { SQLiteTaskScopeStore } from "./adapters/sqlite-task-scope-store.ts";
8
9
  import type { CreateArtifactInput } from "./domain/artifact.ts";
9
10
  import type { Checklist } from "./domain/checklist.ts";
10
11
  import type { TaskEventContext, TaskEventDirection } from "./domain/task-event.ts";
12
+ import type { TaskViewMode } from "./domain/task-scope.ts";
11
13
  import type { ArtifactStore } from "./ports/artifact-store.ts";
12
14
  import type { GateRunner } from "./ports/gate-runner.ts";
13
15
  import type { TaskEventStore } from "./ports/task-event-store.ts";
16
+ import type { TaskScopeStore } from "./ports/task-scope-store.ts";
14
17
  import { projectTaskExecution } from "./task-execution.ts";
15
18
  import { Tasks, type TaskStatus } from "./task-service.ts";
16
- import { TaskAutomationReconciler, taskAutomationSettings, type TaskAutomationSettings } from "./task-automation.ts";
17
19
  import {
18
20
  createArtifactTemplate,
19
21
  createDocument,
@@ -41,8 +43,6 @@ import { instantiateSkillWorkflow } from "./skill-execution.ts";
41
43
 
42
44
  export const EXPECTED_OPERATION_NAMES = [
43
45
  "system.migrate",
44
- "automation.status",
45
- "automation.reconcile",
46
46
  "artifact.create",
47
47
  "artifact.query",
48
48
  "artifact.show",
@@ -52,19 +52,26 @@ export const EXPECTED_OPERATION_NAMES = [
52
52
  "gates.run",
53
53
  "rules.injectable",
54
54
  "tasks.create",
55
+ "tasks.update",
55
56
  "tasks.list",
56
57
  "tasks.graph",
57
58
  "tasks.plan",
58
59
  "tasks.show",
59
60
  "tasks.history",
61
+ "tasks.scope",
62
+ "tasks.set_scope",
63
+ "tasks.assign_project",
60
64
  "tasks.active",
65
+ "tasks.focused",
61
66
  "tasks.focus",
67
+ "tasks.pause",
68
+ "tasks.unpause",
69
+ "tasks.clear_focus",
62
70
  "tasks.start",
63
71
  "tasks.submit",
64
72
  "tasks.complete",
65
73
  "tasks.run_gates",
66
74
  "tasks.set_checklist",
67
- "tasks.set_automation",
68
75
  "tasks.context",
69
76
  "tasks.reject",
70
77
  "tasks.retry",
@@ -117,6 +124,13 @@ function optionalString(input: OperationInput, key: string): string | undefined
117
124
  return value;
118
125
  }
119
126
 
127
+ function optionalStringArray(input: OperationInput, key: string): string[] | undefined {
128
+ const value = input[key];
129
+ if (value === undefined) return undefined;
130
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${key} must be an array of strings`);
131
+ return value as string[];
132
+ }
133
+
120
134
  function optionalNumber(input: OperationInput, key: string): number | undefined {
121
135
  const value = input[key];
122
136
  if (value === undefined) return undefined;
@@ -148,8 +162,8 @@ function handlers(
148
162
  artifacts: ArtifactStore,
149
163
  gates: GateRunner,
150
164
  tasks: Tasks,
151
- automation: TaskAutomationReconciler,
152
165
  events: TaskEventStore,
166
+ scopes: TaskScopeStore,
153
167
  migrate: () => unknown,
154
168
  ): Record<OperationName, OperationHandler> {
155
169
  const eventContext = (input: OperationInput): TaskEventContext => ({
@@ -162,15 +176,19 @@ function handlers(
162
176
  const context = eventContext(input);
163
177
  return { ...context, source: context.source ?? source };
164
178
  };
165
- const taskFilter = (input: OperationInput) => ({
179
+ const artifactFilter = (input: OperationInput) => ({
166
180
  status: optionalString(input, "status"),
167
181
  text: optionalString(input, "text"),
168
182
  limit: optionalNumber(input, "limit"),
169
183
  });
184
+ const taskFilter = (input: OperationInput) => ({
185
+ ...artifactFilter(input),
186
+ projectRoot: string(input, "project_root"),
187
+ scope: optionalString(input, "scope") as TaskViewMode | undefined,
188
+ rootTaskId: optionalString(input, "root_task_id"),
189
+ });
170
190
  return {
171
191
  "system.migrate": () => migrate(),
172
- "automation.status": () => automation.status(),
173
- "automation.reconcile": () => automation.reconcile(),
174
192
  "artifact.create": (input) => {
175
193
  const normalized = normalizeCreateInput(input);
176
194
  if (normalized.kind !== "task") return artifacts.create(normalized);
@@ -183,6 +201,8 @@ function handlers(
183
201
  labels: normalized.labels,
184
202
  extra: normalized.extra,
185
203
  templateId: normalized.templateId,
204
+ projectRoot: string(input, "project_root"),
205
+ projectSource: "cwd",
186
206
  }, eventContextFor(input, "artifact-api"));
187
207
  },
188
208
  "artifact.query": (input) => artifacts.query(input),
@@ -218,7 +238,7 @@ function handlers(
218
238
  ? tasks.runGates(id, eventContextFor(input, "gates-api"))
219
239
  : gates.runAsync(id);
220
240
  },
221
- "rules.injectable": () => listInjectableRules(artifacts, tasks.active()?.id)
241
+ "rules.injectable": (input) => listInjectableRules(artifacts, tasks.active(taskFilter(input))?.id)
222
242
  .map(({ id, title, body, extra }) => ({ id, title, body, extra })),
223
243
  "tasks.create": (input) => tasks.create({
224
244
  title: string(input, "title"),
@@ -231,6 +251,13 @@ function handlers(
231
251
  templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
232
252
  parentId: optionalString(input, "parent_id") ?? optionalString(input, "parentId"),
233
253
  dependsOn: (input["depends_on"] ?? input["dependsOn"]) as string[] | undefined,
254
+ projectRoot: string(input, "project_root"),
255
+ projectSource: "cwd",
256
+ }, eventContext(input)),
257
+ "tasks.update": (input) => tasks.update(string(input, "id"), {
258
+ ...(input["title"] !== undefined ? { title: optionalString(input, "title")! } : {}),
259
+ ...(input["body"] !== undefined ? { body: optionalString(input, "body")! } : {}),
260
+ ...(input["labels"] !== undefined ? { labels: optionalStringArray(input, "labels")! } : {}),
234
261
  }, eventContext(input)),
235
262
  "tasks.list": (input) => tasks.list(taskFilter(input)),
236
263
  "tasks.graph": (input) => tasks.graph(taskFilter(input)),
@@ -241,18 +268,29 @@ function handlers(
241
268
  cursor: optionalNumber(input, "cursor"),
242
269
  direction: optionalString(input, "direction") as TaskEventDirection | undefined,
243
270
  }),
244
- "tasks.active": () => tasks.active(),
245
- "tasks.focus": (input) => tasks.focus(string(input, "id")),
271
+ "tasks.scope": (input) => tasks.scopeSelection(string(input, "project_root")),
272
+ "tasks.set_scope": (input) => tasks.setView(
273
+ string(input, "project_root"),
274
+ string(input, "scope") as TaskViewMode,
275
+ optionalString(input, "root_task_id"),
276
+ ),
277
+ "tasks.assign_project": (input) => tasks.assignProject(
278
+ string(input, "id"),
279
+ string(input, "project_root"),
280
+ eventContext(input),
281
+ ),
282
+ "tasks.active": (input) => tasks.active(taskFilter(input)),
283
+ "tasks.focused": (input) => tasks.focused(taskFilter(input)),
284
+ "tasks.focus": (input) => tasks.focus(string(input, "id"), eventContext(input)),
285
+ "tasks.pause": (input) => tasks.pauseFocus(eventContext(input)),
286
+ "tasks.unpause": (input) => tasks.unpauseFocus(eventContext(input)),
287
+ "tasks.clear_focus": (input) => tasks.clearFocus(eventContext(input)),
246
288
  "tasks.start": (input) => tasks.transition(string(input, "id"), "start", eventContext(input)),
247
289
  "tasks.submit": (input) => tasks.transition(string(input, "id"), "submit", eventContext(input)),
248
290
  "tasks.complete": (input) => tasks.completeAsync(string(input, "id"), eventContext(input)),
249
291
  "tasks.run_gates": (input) => tasks.runGates(string(input, "id"), eventContext(input)),
250
292
  "tasks.set_checklist": (input) => tasks.setChecklist(string(input, "id"), input["checklist"] as Checklist),
251
- "tasks.set_automation": (input) => {
252
- if (typeof input["enabled"] !== "boolean") throw new Error("enabled must be a boolean");
253
- return tasks.setAutomation(string(input, "id"), input["enabled"], eventContext(input));
254
- },
255
- "tasks.context": () => taskContext(artifacts, tasks.active()?.id),
293
+ "tasks.context": (input) => taskContext(artifacts, tasks.active()?.id, new Set(tasks.list(taskFilter(input)).map((task) => task.id))),
256
294
  "tasks.reject": (input) => tasks.transition(string(input, "id"), "reject", eventContext(input)),
257
295
  "tasks.retry": (input) => tasks.transition(string(input, "id"), "retry", eventContext(input)),
258
296
  "tasks.cancel": (input) => tasks.transition(string(input, "id"), "cancel", eventContext(input)),
@@ -263,7 +301,7 @@ function handlers(
263
301
  labels: input["labels"] as string[] | undefined, extra: input["extra"] as Record<string, unknown> | undefined,
264
302
  templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
265
303
  }),
266
- "docs.list": (input) => listDocuments(artifacts, taskFilter(input)),
304
+ "docs.list": (input) => listDocuments(artifacts, artifactFilter(input)),
267
305
  "docs.show": (input) => showDocument(artifacts, string(input, "id")),
268
306
  "docs.activate": (input) => transitionDocument(artifacts, string(input, "id"), "activate"),
269
307
  "docs.archive": (input) => transitionDocument(artifacts, string(input, "id"), "archive"),
@@ -275,7 +313,7 @@ function handlers(
275
313
  severity: optionalString(input, "severity") as "block" | "warn" | "info" | undefined,
276
314
  labels: input["labels"] as string[] | undefined, extra: input["extra"] as Record<string, unknown> | undefined,
277
315
  }),
278
- "rules.list": (input) => listRules(artifacts, taskFilter(input)),
316
+ "rules.list": (input) => listRules(artifacts, artifactFilter(input)),
279
317
  "rules.show": (input) => showRule(artifacts, string(input, "id")),
280
318
  "rules.preview": (input) => previewRule(artifacts, string(input, "id")),
281
319
  "rules.enable": (input) => transitionRule(artifacts, string(input, "id"), "enable"),
@@ -291,28 +329,42 @@ function handlers(
291
329
  title: string(input, "title"), targetKind: string(input, "target_kind"), defaults: input["defaults"] as Record<string, unknown> | undefined,
292
330
  required: input["required"] as string[] | undefined, body: optionalString(input, "body"), labels: input["labels"] as string[] | undefined,
293
331
  }),
294
- "skills.list": (input) => listSkills(artifacts, taskFilter(input)),
332
+ "skills.list": (input) => listSkills(artifacts, artifactFilter(input)),
295
333
  "skills.show": (input) => showSkill(artifacts, string(input, "id")),
296
334
  "skills.invoke": (input) => skillInvocation(artifacts, string(input, "id")),
297
335
  "skills.run": (input) => instantiateSkillWorkflow(artifacts, string(input, "id"), {
298
336
  runId: optionalString(input, "run_id") ?? optionalString(input, "runId"),
299
337
  arguments: input["arguments"] as Record<string, unknown> | undefined,
300
- }, { events, context: eventContextFor(input, "skill-run") }),
338
+ }, { events, scopes, projectRoot: string(input, "project_root"), context: eventContextFor(input, "skill-run") }),
301
339
  "skills.enable": (input) => transitionSkill(artifacts, string(input, "id"), "enable"),
302
340
  "skills.disable": (input) => transitionSkill(artifacts, string(input, "id"), "disable"),
303
- "skills.instantiate": (input) => instantiateTemplate(artifacts, string(input, "template_id"), normalizeCreateInput(input)),
341
+ "skills.instantiate": (input) => {
342
+ const templateId = string(input, "template_id");
343
+ const template = artifacts.get(templateId);
344
+ if (template?.extra["targetKind"] !== "task") return instantiateTemplate(artifacts, templateId, normalizeCreateInput(input));
345
+ return tasks.create({
346
+ title: optionalString(input, "title") as string,
347
+ body: optionalString(input, "body"),
348
+ status: optionalString(input, "status") as TaskStatus | undefined,
349
+ labels: input["labels"] as string[] | undefined,
350
+ extra: input["extra"] as Record<string, unknown> | undefined,
351
+ templateId,
352
+ projectRoot: string(input, "project_root"),
353
+ projectSource: "cwd",
354
+ }, eventContextFor(input, "template-instantiation"));
355
+ },
304
356
  };
305
357
  }
306
358
 
307
- export function createPapyrusService(path: string, options: { automation?: TaskAutomationSettings } = {}): PapyrusService {
359
+ export function createPapyrusService(path: string): PapyrusService {
308
360
  const db = openDb(path);
309
361
  const artifacts = new SQLiteArtifactStore(db);
310
362
  const gates = new SQLiteGateRunner(db);
311
363
  const focus = new SQLiteTaskFocusStore(db);
312
364
  const events = new SQLiteTaskEventStore(db);
313
- const tasks = new Tasks(artifacts, gates, focus, events);
314
- const automation = new TaskAutomationReconciler(tasks, options.automation ?? taskAutomationSettings({}));
315
- const registry = handlers(artifacts, gates, tasks, automation, events, () => migrateDb(db));
365
+ const scopes = new SQLiteTaskScopeStore(db);
366
+ const tasks = new Tasks(artifacts, gates, focus, events, scopes);
367
+ const registry = handlers(artifacts, gates, tasks, events, scopes, () => migrateDb(db));
316
368
  const state = (): SchemaState => {
317
369
  const current = schemaVersion(db);
318
370
  return { current, required: SQLITE_SCHEMA_VERSION, migrationRequired: current !== SQLITE_SCHEMA_VERSION };
@@ -323,8 +375,8 @@ export function createPapyrusService(path: string, options: { automation?: TaskA
323
375
  async execute(operation, input = {}) {
324
376
  const handler = registry[operation as OperationName];
325
377
  if (!handler) throw new UnknownOperationError(`unknown operation "${operation}"`);
326
- if (operation !== "system.migrate" && operation !== "automation.status" && state().migrationRequired) {
327
- throw new MigrationRequiredError("database migration required; run `papyrus migrate task-history`");
378
+ if (operation !== "system.migrate" && state().migrationRequired) {
379
+ throw new MigrationRequiredError("database migration required; run `papyrus migrate task-focus`");
328
380
  }
329
381
  return handler(input);
330
382
  },
@@ -11,6 +11,8 @@ import {
11
11
  import type { ArtifactStore } from "./ports/artifact-store.ts";
12
12
  import type { TaskEventContext } from "./domain/task-event.ts";
13
13
  import type { TaskEventStore } from "./ports/task-event-store.ts";
14
+ import type { TaskScopeStore } from "./ports/task-scope-store.ts";
15
+ import { normalizeProjectRoot } from "./domain/task-scope.ts";
14
16
  import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
15
17
  import { projectTaskExecution, type TaskExecutionPlan } from "./task-execution.ts";
16
18
  import type { TaskGraph, TaskNode, TaskStatus } from "./task-service.ts";
@@ -117,9 +119,10 @@ export function instantiateSkillWorkflow(
117
119
  artifacts: ArtifactStore,
118
120
  skillId: string,
119
121
  input: InstantiateSkillWorkflowInput = {},
120
- history?: { events: TaskEventStore; context?: TaskEventContext },
122
+ history?: { events: TaskEventStore; scopes: TaskScopeStore; projectRoot: string; context?: TaskEventContext },
121
123
  ): SkillWorkflowRunResult {
122
124
  const { definition } = requireWorkflowSkill(artifacts, skillId);
125
+ const projectRoot = history ? normalizeProjectRoot(history.projectRoot) : undefined;
123
126
  const arguments_ = resolveSkillArguments(definition, input.arguments);
124
127
  const rendered = renderDefinition(definition, arguments_);
125
128
  const runId = normalizeRunId(skillId, input.runId);
@@ -175,15 +178,18 @@ export function instantiateSkillWorkflow(
175
178
  labels: withRunLabel(blueprint.labels, runId),
176
179
  extra: { ...(blueprint.extra ?? {}), skillRun: { id: runId, skillId, ref: blueprint.ref } },
177
180
  });
178
- if (history) history.events.append({
179
- taskId: task.id,
180
- type: "created",
181
- actor: history.context?.actor ?? "system",
182
- source: history.context?.source ?? "skill-run",
183
- toStatus: task.status as TaskStatus,
184
- ...(history.context?.sessionId === undefined ? {} : { sessionId: history.context.sessionId }),
185
- ...(history.context?.reason === undefined ? {} : { reason: history.context.reason }),
186
- });
181
+ if (history) {
182
+ history.scopes.assign(task.id, projectRoot, "cwd");
183
+ history.events.append({
184
+ taskId: task.id,
185
+ type: "created",
186
+ actor: history.context?.actor ?? "system",
187
+ source: history.context?.source ?? "skill-run",
188
+ toStatus: task.status as TaskStatus,
189
+ ...(history.context?.sessionId === undefined ? {} : { sessionId: history.context.sessionId }),
190
+ ...(history.context?.reason === undefined ? {} : { reason: history.context.reason }),
191
+ });
192
+ }
187
193
  return task;
188
194
  });
189
195
 
@@ -34,8 +34,10 @@ function renderCurrent(task: Artifact): string[] {
34
34
  ];
35
35
  }
36
36
 
37
- export function taskContext(artifacts: ArtifactStore, activeTaskId?: string): string | null {
38
- const tasks = artifacts.query({ kind: "task" }).sort((left, right) => left.updated_at.localeCompare(right.updated_at));
37
+ export function taskContext(artifacts: ArtifactStore, activeTaskId?: string, taskIds?: Set<string>): string | null {
38
+ const tasks = artifacts.query({ kind: "task" })
39
+ .filter((task) => taskIds === undefined || taskIds.has(task.id))
40
+ .sort((left, right) => left.updated_at.localeCompare(right.updated_at));
39
41
  const open = tasks.filter((task) => task.status !== "done" && task.status !== "canceled");
40
42
  if (open.length === 0) return null;
41
43