@agent-plan/server 0.2.29 → 0.3.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAuMpC,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACrC;AAw5CD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,KAAK,CAAC;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA6EvE"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAyJpC,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACrC;AA+6CD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,KAAK,CAAC;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA6EvE"}
package/dist/serve.js CHANGED
@@ -7,15 +7,11 @@ import { fileURLToPath } from "node:url";
7
7
  import { createAdaptorServer } from "@hono/node-server";
8
8
  import { Hono } from "hono";
9
9
  import { cors } from "hono/cors";
10
- import { ExportService, PlanStore, PlanStoreError, PlanStaleWriteError, PlanWriterBusyError, createFeatureId, createPhaseId, createChecklistItemId, createRequirementId, createTaskId, findPhaseByRef, normalizeSlug, withFeatureLock, needsMotivation, checkExplicitTaskStart, recommendNextTask, recommendNextWork, reconcileRequirementMacroTasks, RequirementMacroTaskError, packageVersionFromModule } from "@agent-plan/core";
10
+ import { ExportService, PlanStore, PlanStoreError, PlanStaleWriteError, PlanWriterBusyError, createFeatureId, createPhaseId, createChecklistItemId, createRequirementId, createTaskId, findPhaseByRef, normalizeSlug, withFeatureLock, needsMotivation, checkExplicitTaskStart, recommendNextTask, recommendNextWork, reconcileRequirementMacroTasks, RequirementMacroTaskError, packageVersionFromModule, ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE, ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, deleteFeatureCascade, nowISO, applyTaskLifecycleDates, resolveAcceptedDecisionTarget } from "@agent-plan/core";
11
11
  import { WsHub } from "./ws-hub.js";
12
12
  // ─── Watcher ────────────────────────────────────────────────────────────
13
13
  let watcherAbort = null;
14
- let watcherHubRef = { current: null };
15
14
  const SERVER_PACKAGE = packageVersionFromModule(import.meta.url, "@agent-plan/server");
16
- function nowISO() {
17
- return new Date().toISOString();
18
- }
19
15
  /** Resolve requirement phase refs before persistence so requirements never dangle. */
20
16
  async function resolveRequirementPhaseIds(store, linkedPhaseIds) {
21
17
  if (!Array.isArray(linkedPhaseIds) || linkedPhaseIds.length === 0) {
@@ -46,40 +42,27 @@ function noMutableFieldsResponse(fields) {
46
42
  mutableFields: [...fields],
47
43
  };
48
44
  }
49
- async function resolveAcceptedDecisionTarget(store, targetType, rawTargetRef) {
50
- if (targetType === "project")
51
- return { ok: true, owner: { kind: "project" }, targetRef: "project", event: { type: "project" } };
52
- const targetRef = rawTargetRef?.trim();
53
- if (!targetRef)
54
- return { ok: false, error: `targetRef is required for ${targetType} accepted decisions` };
55
- const [featuresDocument, phases] = await Promise.all([store.loadFeatures(), store.loadAllPhases()]);
56
- const features = featuresDocument.features;
57
- if (targetType === "feature") {
58
- const feature = features.find((entry) => entry.id === targetRef || `F${String(entry.number).padStart(3, "0")}`.toLowerCase() === targetRef.toLowerCase() || entry.shortId?.toLowerCase() === targetRef.toLowerCase());
59
- if (!feature)
60
- return { ok: false, error: `feature not found: ${targetRef}` };
61
- return { ok: true, owner: { kind: "feature", featureId: feature.id }, targetRef: `F${String(feature.number).padStart(3, "0")}`, event: { type: "feature", featureId: feature.id } };
62
- }
63
- if (targetType === "phase") {
64
- const phase = findPhaseByRef(phases, features, targetRef);
65
- if (!phase)
66
- return { ok: false, error: `phase not found: ${targetRef}` };
67
- return { ok: true, owner: { kind: "phase", phaseId: phase.id }, targetRef: `P${String(phase.number).padStart(3, "0")}`, event: { type: "phase", phaseId: phase.id, featureId: phase.featureId ?? "" } };
68
- }
69
- const found = phases.flatMap((phase) => phase.tasks.map((task) => ({ phase, task }))).find(({ task }) => task.id === targetRef)
70
- ?? (() => {
71
- const taskNumberMatch = targetRef.match(/^T(\d+)$/i);
72
- return taskNumberMatch
73
- ? phases.flatMap((phase) => phase.tasks.map((task) => ({ phase, task }))).find(({ task }) => task.number === Number(taskNumberMatch[1]))
74
- : undefined;
75
- })();
76
- if (!found)
77
- return { ok: false, error: `task not found: ${targetRef}` };
78
- return { ok: true, owner: { kind: "task", phaseId: found.phase.id, taskId: found.task.id }, targetRef: `P${String(found.phase.number).padStart(3, "0")}/T${String(found.task.number).padStart(3, "0")}`, event: { type: "task", phaseId: found.phase.id, taskId: found.task.id, featureId: found.phase.featureId ?? "" } };
79
- }
80
- function nextTaskNumber(phase) {
81
- const numbers = phase.tasks.map((task) => task.number || 0).filter((n) => Number.isFinite(n));
82
- return (numbers.length > 0 ? Math.max(...numbers) : 0) + 1;
45
+ /**
46
+ * Resolve an accepted-decision target with the same ref grammar the adapters
47
+ * use (plan-core's resolveAcceptedDecisionTarget: composite/shortId/title for
48
+ * tasks, exact/partial name with ambiguity detection for features), then add
49
+ * the WebSocket broadcast envelope this HTTP layer alone needs. Resolution
50
+ * itself is never re-derived here — only the event shape is server-local.
51
+ */
52
+ async function resolveAcceptedDecisionEventTarget(store, targetType, rawTargetRef) {
53
+ const target = await resolveAcceptedDecisionTarget(store, targetType, rawTargetRef);
54
+ if (!target.ok)
55
+ return target;
56
+ const { owner, ref: targetRef } = target;
57
+ if (owner.kind === "project")
58
+ return { ok: true, owner, targetRef, event: { type: "project" } };
59
+ if (owner.kind === "feature")
60
+ return { ok: true, owner, targetRef, event: { type: "feature", featureId: owner.featureId } };
61
+ const phase = await store.loadPhase(owner.phaseId).catch(() => null);
62
+ const featureId = phase?.featureId ?? "";
63
+ if (owner.kind === "phase")
64
+ return { ok: true, owner, targetRef, event: { type: "phase", phaseId: owner.phaseId, featureId } };
65
+ return { ok: true, owner, targetRef, event: { type: "task", phaseId: owner.phaseId, taskId: owner.taskId, featureId } };
83
66
  }
84
67
  function isPrivateIpv4(address) {
85
68
  return /^10\./.test(address) || /^192\.168\./.test(address) || /^172\.(1[6-9]|2\d|3[01])\./.test(address);
@@ -129,29 +112,12 @@ function phaseGovernanceReady(phase) {
129
112
  function fromWebUi(c) {
130
113
  return c.req.header("X-Planner-Source") === "web-ui";
131
114
  }
132
- function applyTaskLifecycleDates(task, nextStatus, now) {
133
- const previousStatus = task.status;
134
- if (nextStatus === "in-progress" && !task.startedAt) {
135
- task.startedAt = now;
136
- }
137
- if (nextStatus === "done") {
138
- if (!task.startedAt)
139
- task.startedAt = now;
140
- task.completedAt = now;
141
- }
142
- else if (previousStatus === "done") {
143
- task.completedAt = "";
144
- }
145
- task.status = nextStatus;
146
- return task;
147
- }
148
115
  function startWatcher(planRoot, hubRef) {
149
116
  stopWatcher();
150
117
  if (!existsSync(planRoot))
151
118
  return;
152
119
  const ac = new AbortController();
153
120
  watcherAbort = ac;
154
- watcherHubRef = hubRef;
155
121
  try {
156
122
  const watcher = watch(planRoot, { recursive: true, signal: ac.signal }, (_event, filename) => {
157
123
  if (filename && !filename.includes(".tmp.")) {
@@ -171,13 +137,6 @@ function stopWatcher() {
171
137
  watcherAbort?.abort();
172
138
  watcherAbort = null;
173
139
  }
174
- // ── Helpers ─────────────────────────────────────────────────────────
175
- async function propagateTaskStatus(phaseId) {
176
- // This is now handled by PlanStore.syncStatuses()
177
- }
178
- async function propagatePhaseStatus(featureId) {
179
- // This is now handled by PlanStore.syncStatuses()
180
- }
181
140
  function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
182
141
  const hub = () => hubRef.current;
183
142
  const route = (path) => `${apiPrefix}${path}`;
@@ -248,7 +207,12 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
248
207
  const body = await c.req.json();
249
208
  const existingProject = await store.loadProject();
250
209
  if (body.acceptedDecisions !== undefined && JSON.stringify(body.acceptedDecisions) !== JSON.stringify(existingProject.acceptedDecisions))
251
- return c.json({ updated: false, errorCode: "ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED", message: "Raw acceptedDecisions replacement is disabled. Use the accepted-decisions semantic create, update, or delete endpoints so IDs and acceptedAt are preserved." }, 400);
210
+ return c.json({ updated: false, errorCode: ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, message: ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE }, 400);
211
+ // The legacy free-form `decisions` array stays writable over HTTP. It is an
212
+ // agent-governance rule, and .planner/SKILL.md makes the Web UI a human-
213
+ // supervisor surface that may bypass agent-only gates. Blocking it here also
214
+ // blocked the only way to seed the legacy state the planner-load migration
215
+ // exists to consume. The Pi and MCP tools still refuse it.
252
216
  const mutableFields = ["name", "goal", "description", "descriptionRef", "webPort", "scope", "outOfScope", "decisions", "globalRules", "technologies", "tools", "contentLanguage", "chatLanguage", "workflowRules", "projectGuidelines"];
253
217
  if (!hasDefinedField(body, mutableFields))
254
218
  return c.json(noMutableFieldsResponse(mutableFields), 400);
@@ -311,7 +275,7 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
311
275
  const body = await c.req.json();
312
276
  if (!body.targetType)
313
277
  return c.json({ error: "targetType required" }, 400);
314
- const target = await resolveAcceptedDecisionTarget(store, body.targetType, body.targetRef);
278
+ const target = await resolveAcceptedDecisionEventTarget(store, body.targetType, body.targetRef);
315
279
  if (!target.ok)
316
280
  return c.json({ error: target.error, created: false }, 404);
317
281
  const createInput = {
@@ -333,7 +297,7 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
333
297
  const mutableFields = ["title", "decision", "rationale", "implementationNotes"];
334
298
  if (!hasDefinedField(body, mutableFields))
335
299
  return c.json(noMutableFieldsResponse(mutableFields), 400);
336
- const target = await resolveAcceptedDecisionTarget(store, body.targetType, body.targetRef);
300
+ const target = await resolveAcceptedDecisionEventTarget(store, body.targetType, body.targetRef);
337
301
  if (!target.ok)
338
302
  return c.json({ error: target.error, updated: false }, 404);
339
303
  const updateInput = {
@@ -352,7 +316,7 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
352
316
  const body = await c.req.json().catch(() => ({}));
353
317
  if (!body.targetType)
354
318
  return c.json({ error: "targetType required", deleted: false }, 400);
355
- const target = await resolveAcceptedDecisionTarget(store, body.targetType, body.targetRef);
319
+ const target = await resolveAcceptedDecisionEventTarget(store, body.targetType, body.targetRef);
356
320
  if (!target.ok)
357
321
  return c.json({ error: target.error, deleted: false }, 404);
358
322
  if (body.confirmed !== true)
@@ -639,7 +603,7 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
639
603
  if (body.status !== undefined && body.status !== existing.status)
640
604
  return c.json({ updated: false, errorCode: "DERIVED_STATUS_READ_ONLY", message: "Feature status is derived from child phases and tasks and cannot be updated directly." }, 400);
641
605
  if (body.acceptedDecisions !== undefined && JSON.stringify(body.acceptedDecisions) !== JSON.stringify(existing.acceptedDecisions))
642
- return c.json({ updated: false, errorCode: "ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED", message: "Raw acceptedDecisions replacement is disabled. Use the accepted-decisions semantic create, update, or delete endpoints so IDs and acceptedAt are preserved." }, 400);
606
+ return c.json({ updated: false, errorCode: ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, message: ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE }, 400);
643
607
  const mutableFields = ["name", "description", "descriptionRef", "discussedAt", "contextReady", "contextReadyReason", "startDate", "endDate", "workDone", "workRemaining", "priority", "dependsOn"];
644
608
  if (!hasDefinedField(body, mutableFields))
645
609
  return c.json(noMutableFieldsResponse(mutableFields), 400);
@@ -668,11 +632,21 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
668
632
  }));
669
633
  app.delete(route("/features/:id"), async (c) => {
670
634
  const id = c.req.param("id");
671
- await store.updateFeatures((doc) => {
672
- doc.features = doc.features.filter((f) => f.id !== id);
673
- return doc;
674
- });
675
- await store.writeGenerated();
635
+ if (!id)
636
+ return c.json({ error: "id required" }, 400);
637
+ const cascade = c.req.query("cascade") === "true";
638
+ const outcome = await deleteFeatureCascade(store, id, { cascade });
639
+ if (!outcome.ok) {
640
+ // FEATURE_NOT_FOUND: no such feature, or it vanished between resolve
641
+ // and write (race). Either way, this is a 404 — never a reported
642
+ // success for a delete that did not happen (same defect class as
643
+ // T413).
644
+ return c.json({ error: outcome.error }, 404);
645
+ }
646
+ const { result } = outcome;
647
+ for (const phase of result.phases) {
648
+ hub()?.broadcast({ type: "phases-updated", data: { action: phase.action === "deleted" ? "deleted" : "updated", id: phase.id, phaseId: phase.id, featureId: phase.action === "deleted" ? id : "" } });
649
+ }
676
650
  hub()?.broadcast({ type: "features-updated", data: { action: "deleted", id, featureId: id } });
677
651
  hub()?.broadcast({ type: "plan-rendered", data: {} });
678
652
  await store.syncStatuses();
@@ -725,7 +699,6 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
725
699
  return c.json({ error: "featureId required: a phase must belong to a feature" }, 400);
726
700
  let phase;
727
701
  await store.runBatch(() => withFeatureLock(featureId, async () => {
728
- const allPhases = await store.loadAllPhases();
729
702
  const slug = normalizeSlug(title);
730
703
  const id = createPhaseId();
731
704
  const identity = await store.allocateEntityIdentity("phase", id);
@@ -816,7 +789,12 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
816
789
  if (body.status !== undefined && body.status !== existingPhase.status)
817
790
  return c.json({ updated: false, errorCode: "DERIVED_STATUS_READ_ONLY", message: "Phase status is derived from child tasks and cannot be updated directly." }, 400);
818
791
  if (body.acceptedDecisions !== undefined && JSON.stringify(body.acceptedDecisions) !== JSON.stringify(existingPhase.acceptedDecisions))
819
- return c.json({ updated: false, errorCode: "ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED", message: "Raw acceptedDecisions replacement is disabled. Use the accepted-decisions semantic create, update, or delete endpoints so IDs and acceptedAt are preserved." }, 400);
792
+ return c.json({ updated: false, errorCode: ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, message: ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE }, 400);
793
+ // The legacy free-form `decisions` array stays writable over HTTP. It is an
794
+ // agent-governance rule, and .planner/SKILL.md makes the Web UI a human-
795
+ // supervisor surface that may bypass agent-only gates. Blocking it here also
796
+ // blocked the only way to seed the legacy state the planner-load migration
797
+ // exists to consume. The Pi and MCP tools still refuse it.
820
798
  let nextFeatureId = existingPhase.featureId;
821
799
  if (body.featureId !== undefined) {
822
800
  nextFeatureId = body.featureId.trim() || undefined;
@@ -1280,7 +1258,12 @@ function createApiApp(store, hubRef, apiPrefix = "", uiConfig, isBusy) {
1280
1258
  if (!existing)
1281
1259
  return c.json({ error: "task not found" }, 404);
1282
1260
  if (body.acceptedDecisions !== undefined && JSON.stringify(body.acceptedDecisions) !== JSON.stringify(existing.acceptedDecisions))
1283
- return c.json({ updated: false, errorCode: "ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED", message: "Raw acceptedDecisions replacement is disabled. Use the accepted-decisions semantic create, update, or delete endpoints so IDs and acceptedAt are preserved." }, 400);
1261
+ return c.json({ updated: false, errorCode: ACCEPTED_DECISION_SEMANTIC_MUTATION_REQUIRED_ERROR_CODE, message: ACCEPTED_DECISION_RAW_REPLACEMENT_DISABLED_MESSAGE }, 400);
1262
+ // The legacy free-form `decisions` array stays writable over HTTP. It is an
1263
+ // agent-governance rule, and .planner/SKILL.md makes the Web UI a human-
1264
+ // supervisor surface that may bypass agent-only gates. Blocking it here also
1265
+ // blocked the only way to seed the legacy state the planner-load migration
1266
+ // exists to consume. The Pi and MCP tools still refuse it.
1284
1267
  const mutableFields = ["title", "description", "descriptionRef", "status", "notes", "decisions", "priority", "checklist", "subtasks"];
1285
1268
  if (!hasDefinedField(body, mutableFields))
1286
1269
  return c.json(noMutableFieldsResponse(mutableFields), 400);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/server",
3
- "version": "0.2.29",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "Local HTTP/WebSocket server for Agent Plan dashboards and planner APIs.",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "@hono/node-server": "^1.14.0",
42
42
  "hono": "^4.7.0",
43
43
  "ws": "^8.18.0",
44
- "@agent-plan/core": "0.2.29"
44
+ "@agent-plan/core": "0.3.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/ws": "^8.18.0"