@agentistics/mcp 2.39.0 → 2.41.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.
@@ -21,7 +21,7 @@ function isLocalModelId(modelId) {
21
21
  const id = String(modelId ?? "").toLowerCase();
22
22
  return LOCAL_RUNTIME_PREFIXES.some((p) => id.startsWith(p));
23
23
  }
24
- var LOCAL_MODEL_PRICE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
24
+ var LOCAL_MODEL_PRICE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cacheWrite1h: 0 };
25
25
 
26
26
  // ../core/src/types.ts
27
27
  var HARNESS_SORT = {
@@ -33,7 +33,7 @@ var HARNESS_SORT = {
33
33
  kimi: 5
34
34
  };
35
35
  var HARNESS_ORDER = Object.keys(HARNESS_SORT).sort((a, b) => HARNESS_SORT[a] - HARNESS_SORT[b]);
36
- var MODEL_PRICING = {
36
+ var BASE_MODEL_PRICING = {
37
37
  "claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
38
38
  "claude-mythos-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
39
39
  "claude-opus-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
@@ -67,6 +67,7 @@ var MODEL_PRICING = {
67
67
  "gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0.25 },
68
68
  "gpt-5": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 1.25 }
69
69
  };
70
+ var MODEL_PRICING = Object.fromEntries(Object.entries(BASE_MODEL_PRICING).map(([id, p]) => [id, { ...p, cacheWrite1h: p.input * 2 }]));
70
71
  function getModelPrice(modelId) {
71
72
  if (MODEL_PRICING[modelId])
72
73
  return MODEL_PRICING[modelId];
@@ -87,7 +88,7 @@ function getModelPrice(modelId) {
87
88
  const hit = forwardKey || reverseKey;
88
89
  if (hit)
89
90
  return MODEL_PRICING[hit];
90
- return { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
91
+ return { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75, cacheWrite1h: 6 };
91
92
  }
92
93
  function sessionModelUsage(s, fallbackModel) {
93
94
  const breakdown = s.model_usage;
@@ -104,6 +105,8 @@ function sessionModelUsage(s, fallbackModel) {
104
105
  outputTokens: s.output_tokens ?? 0,
105
106
  cacheReadInputTokens: s.cache_read_input_tokens ?? 0,
106
107
  cacheCreationInputTokens: s.cache_creation_input_tokens ?? 0,
108
+ cacheCreation1hInputTokens: s.cache_creation_1h_input_tokens,
109
+ cacheCreation5mInputTokens: s.cache_creation_5m_input_tokens,
107
110
  webSearchRequests: 0,
108
111
  costUSD: 0
109
112
  }]];
@@ -116,7 +119,9 @@ function sessionCostUSD(s, fallbackModel) {
116
119
  }
117
120
  function calcCost(usage, modelId) {
118
121
  const price = getModelPrice(modelId);
119
- return usage.inputTokens / 1e6 * price.input + usage.outputTokens / 1e6 * price.output + usage.cacheReadInputTokens / 1e6 * price.cacheRead + usage.cacheCreationInputTokens / 1e6 * price.cacheWrite;
122
+ const hasTtlBreakdown = usage.cacheCreation1hInputTokens !== undefined && usage.cacheCreation5mInputTokens !== undefined;
123
+ const cacheWriteCost = hasTtlBreakdown ? (usage.cacheCreation1hInputTokens ?? 0) / 1e6 * price.cacheWrite1h + (usage.cacheCreation5mInputTokens ?? 0) / 1e6 * price.cacheWrite : usage.cacheCreationInputTokens / 1e6 * price.cacheWrite;
124
+ return usage.inputTokens / 1e6 * price.input + usage.outputTokens / 1e6 * price.output + usage.cacheReadInputTokens / 1e6 * price.cacheRead + cacheWriteCost;
120
125
  }
121
126
  // ../core/src/team.ts
122
127
  var DEFAULT_TEAM = Object.freeze({
@@ -373,6 +378,9 @@ var HARNESS_MODELS = {
373
378
  var SPAWN_SPECS_MODEL_IDS = Object.fromEntries(Object.entries(HARNESS_MODELS).map(([h, opts]) => [h, opts.map((o) => o.id)]));
374
379
  // ../core/src/attachments.ts
375
380
  var IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp", "svg", "avif"]);
381
+ // ../core/src/pasteSanitize.ts
382
+ var BRACKETED_PASTE_START = "\x1B[200~";
383
+ var MARKER_LEN = BRACKETED_PASTE_START.length;
376
384
  // session-tokens.ts
377
385
  function sessionHarness(s) {
378
386
  return s.harness ?? "claude";
@@ -578,14 +586,17 @@ var TOOLS = [
578
586
  },
579
587
  {
580
588
  name: "agentistics_task_subtask",
581
- description: "BETA — the task board is new and still changing; its shapes may move between releases. Add a subtask, or change one. Pass `title` to add; pass `id` with `done`, `status`, `assignee`, `dueDate`, `startDate` or `blockedBy` to edit one. A subtask carries the same columns its parent does; its own cost, tokens, rounds and harness are rolled up from the sessions filed under it — the PARENT task's rollup is the sum of all its subtasks' sessions. A subtask CAN be blocked by another subtask of the SAME parent (pass `blockedBy` as the full list of sibling subtask ids that must be `done` first; a sibling outside this task, or the subtask itself, is dropped rather than accepted) — this is separate from agentistics_task_blocked_by, which blocks a whole TASK on other tasks. **`done` REQUIRES the subtask to have at least one session filed under it** and is refused (422, `done_needs_session`) without one. To put a session ON a subtask use agentistics_task_session with `subtaskId` — a subtask holds ANY NUMBER of sessions, so the link lives on the session, not in a field here.",
589
+ description: "BETA — the task board is new and still changing; its shapes may move between releases. Add a subtask, or change one. Pass `title` to add; pass `id` with `done`, `status`, `assignee`, `dueDate`, `startDate`, `blockedBy` or `parentGroupId` to edit one. A subtask carries the same columns its parent does; its own cost, tokens, rounds and harness are rolled up from the sessions filed under it — the PARENT task's rollup is the sum of all its subtasks' sessions. A subtask CAN be blocked by another subtask of the SAME parent (pass `blockedBy` as the full list of sibling subtask ids that must be `done` first; a sibling outside this task, or the subtask itself, is dropped rather than accepted) — this is separate from agentistics_task_blocked_by, which blocks a whole TASK on other tasks. **`done` REQUIRES the subtask to have at least one session filed under it** and is refused (422, `done_needs_session`) without one — EXCEPT for a group MEMBER (`parentGroupId` set), which can never hold a session of its own (see below) and therefore reaches `done` through this same edit with no session required; a GROUP itself (`isGroup: true`) and an ordinary loose subtask both still need one. To put a session ON a subtask use agentistics_task_session with `subtaskId` — a subtask holds ANY NUMBER of sessions, so the link lives on the session, not in a field here. **SUBTASK GROUPS are a real hierarchy level, not a label**: pass `isGroup: true` with `title` to create a GROUP — a peer row that CAN hold a session, exactly like a loose subtask. Pass `id` (a subtask) with `parentGroupId` (a group's own subtask id, from the SAME parent task) to make it a MEMBER of that group — join with the group's id, leave with `parentGroupId: ''`. A refused reference (the id names no subtask, names one that is not `isGroup: true`, names a different task, or the subtask being patched is itself a group) is refused as `invalid_group` (422). **Joining a group is ALSO refused (422, `subtask_has_sessions`) when the subtask already has a session filed on it** — a member gets no rollup bucket of its own, so that session's cost would silently drop out of every visible breakdown while the task's own total kept counting it; detach the session first. **A MEMBER can never hold a session of its own** — filing on one is refused by agentistics_task_session with `subtask_in_group` (422); only the group itself accounts for a session, and a member's own status/assignee/dates/comments still exist and feed the group's progress percentage.",
582
590
  inputSchema: {
583
591
  type: "object",
584
592
  properties: {
585
593
  ref: { type: "string" },
586
594
  title: { type: "string" },
587
595
  id: { type: "string" },
588
- done: { type: "boolean" },
596
+ done: {
597
+ type: "boolean",
598
+ description: "Send ALONE with `id` (no other field) for the tick — `{id, done}` is a fast path straight to the done/todo transition. Sent TOGETHER with `parentGroupId` (or any other named field below) in the same call, `done` is silently dropped rather than applied — this tool forwards only the named fields when one is present. Send them as two separate calls instead: join/leave the group first, then set `done` in its own call."
599
+ },
589
600
  status: {
590
601
  type: "string",
591
602
  enum: ["backlog", "todo", "in_progress", "blocked", "in_review", "done", "abandoned"]
@@ -597,6 +608,14 @@ var TOOLS = [
597
608
  type: "array",
598
609
  items: { type: "string" },
599
610
  description: "Full replacement list of SIBLING subtask ids (same parent task) that must be done before this one. Requires `id` — a subtask being created has no siblings to name yet."
611
+ },
612
+ isGroup: {
613
+ type: "boolean",
614
+ description: "Create this subtask as a GROUP instead of an ordinary subtask. Only meaningful together with `title` (creation) — a group's shape cannot be changed afterwards."
615
+ },
616
+ parentGroupId: {
617
+ type: "string",
618
+ description: "Join this subtask (named by `id`) to the group whose id this names — a group of the SAME parent task. Pass an empty string to leave the current group. Refused (422, `invalid_group`) if the id is not an actual group of this task, or if this subtask is itself a group. Refused (422, `subtask_has_sessions`) if this subtask already has a session filed on it — detach it first. **Do not send `done` in this same call** — see `done`'s own description; make it a second call."
600
619
  }
601
620
  },
602
621
  required: ["ref"]
@@ -686,7 +705,7 @@ var TOOLS = [
686
705
  },
687
706
  {
688
707
  name: "agentistics_task_session",
689
- description: "BETA — the task board is new and still changing; its shapes may move between releases. File a SESSION under a task, which is what makes the task measurable: its cost, tokens, rounds and harness all come from the sessions filed under it. Pass `subtaskId` to file it under one of the task's subtasks (the unit of WORK, for a task broken into steps); omit it to file the session directly on the task itself (the unit of DELIVERY, for a task simple enough not to need subtasks). Both can be used on the same task — some sessions direct, some under subtasks — and each subtask's own rollup plus the directly-filed sessions' own rollup are both readable via agentistics_task's `subtaskRollups` (keyed by subtask id, with `id: null` for the direct ones), so the breakdown never disappears. Use agentistics_task to read the subtasks, or agentistics_task_subtask to create one, then pass `ref` + `sessionId` (a managed session id or conversation id) + optionally `subtaskId`. Pass `detach` with a session id to unfile one. The task inherits the session's repository when it has none.",
708
+ description: "BETA — the task board is new and still changing; its shapes may move between releases. File a SESSION under a task, which is what makes the task measurable: its cost, tokens, rounds and harness all come from the sessions filed under it. Pass `subtaskId` to file it under one of the task's subtasks (the unit of WORK, for a task broken into steps) — this INCLUDES a subtask GROUP (see agentistics_task_subtask's `isGroup`), which holds a session exactly like any other subtask; omit `subtaskId` to file the session directly on the task itself (the unit of DELIVERY, for a task simple enough not to need subtasks). Both can be used on the same task — some sessions direct, some under subtasks — and each subtask's own rollup plus the directly-filed sessions' own rollup are both readable via agentistics_task's `subtaskRollups` (keyed by subtask id, with `id: null` for the direct ones), so the breakdown never disappears. **Filing on a subtask that is a MEMBER of a group (has a `parentGroupId`) is refused (422, `subtask_in_group`)** — a member can never hold a session, only the group itself can; file on the group's own id instead. Use agentistics_task to read the subtasks, or agentistics_task_subtask to create one, then pass `ref` + `sessionId` (a managed session id or conversation id) + optionally `subtaskId`. Pass `detach` with a session id to unfile one. The task inherits the session's repository when it has none.",
690
709
  inputSchema: {
691
710
  type: "object",
692
711
  properties: {
@@ -948,10 +967,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
948
967
  }
949
968
  case "agentistics_task_subtask": {
950
969
  const a = args;
951
- const cols = ["status", "assignee", "dueDate", "startDate", "title"];
970
+ const cols = ["status", "assignee", "dueDate", "startDate", "title", "parentGroupId"];
952
971
  const named = cols.filter((c) => typeof a?.[c] === "string");
953
972
  const blockedBy = Array.isArray(a?.blockedBy) ? { blockedBy: a.blockedBy } : {};
954
- const payload = a?.id !== undefined ? named.length > 0 || Array.isArray(a?.blockedBy) ? { id: a.id, ...Object.fromEntries(named.map((c) => [c, a[c]])), ...blockedBy } : { id: a.id, done: a?.done === true } : { title: a?.title };
973
+ const payload = a?.id !== undefined ? named.length > 0 || Array.isArray(a?.blockedBy) ? { id: a.id, ...Object.fromEntries(named.map((c) => [c, a[c]])), ...blockedBy } : { id: a.id, done: a?.done === true } : { title: a?.title, ...a?.isGroup === true ? { isGroup: true } : {} };
955
974
  const body = await apiSend("POST", `/api/tasks/${encodeURIComponent(String(a?.ref))}/subtasks`, payload);
956
975
  return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
957
976
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentistics/mcp",
3
- "version": "2.39.0",
3
+ "version": "2.41.0",
4
4
  "description": "Agentistics MCP server — Claude Code analytics for Claude Desktop and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./dist/agentistics-mcp.js",