@costgate/momentum-mcp 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -9,7 +9,7 @@ MOMENTUM_TOKEN=mom_agent_xxx \
9
9
  ```
10
10
 
11
11
  整条命令不用自己拼 —— 在 Momentum 的「设置 › Agent」里签发令牌时会直接给出,
12
- 默认用 `@latest`,发新版不用改配置;想固定某一版时把 latest 换成版本号(当前是 0.2.1)。
12
+ 默认用 `@latest`,发新版不用改配置;想固定某一版时把 latest 换成版本号(当前是 0.2.3)。
13
13
 
14
14
  | 环境变量 | 必填 | 说明 |
15
15
  |---|---|---|
@@ -18,4 +18,4 @@ MOMENTUM_TOKEN=mom_agent_xxx \
18
18
  | `MOMENTUM_CF_ACCESS_CLIENT_ID` | 否 | 部署在 Cloudflare Access 后面时才需要 |
19
19
  | `MOMENTUM_CF_ACCESS_CLIENT_SECRET` | 否 | 同上 |
20
20
 
21
- 版本 0.2.1。客户端和服务端版本不一致时,启动日志(stderr)会明确警告。
21
+ 版本 0.2.3。客户端和服务端版本不一致时,启动日志(stderr)会明确警告。
package/momentum-mcp.mjs CHANGED
@@ -15339,7 +15339,7 @@ var allowErrorProps = SuperJSON.allowErrorProps;
15339
15339
 
15340
15340
  // src/version.ts
15341
15341
  var UNKNOWN = "unknown";
15342
- var MCP_VERSION = true ? "0.2.1" : versionFromRepo();
15342
+ var MCP_VERSION = true ? "0.2.3" : versionFromRepo();
15343
15343
  var CLIENT_HEADER = `momentum-mcp/${MCP_VERSION}`;
15344
15344
  function compareVersions(a, b) {
15345
15345
  const parse4 = (v) => {
@@ -22646,6 +22646,228 @@ var McpZodTypeKind;
22646
22646
  McpZodTypeKind2["Completable"] = "McpCompletable";
22647
22647
  })(McpZodTypeKind || (McpZodTypeKind = {}));
22648
22648
 
22649
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
22650
+ var MAX_TEMPLATE_LENGTH = 1e6;
22651
+ var MAX_VARIABLE_LENGTH = 1e6;
22652
+ var MAX_TEMPLATE_EXPRESSIONS = 1e4;
22653
+ var MAX_REGEX_LENGTH = 1e6;
22654
+ var UriTemplate = class _UriTemplate {
22655
+ /**
22656
+ * Returns true if the given string contains any URI template expressions.
22657
+ * A template expression is a sequence of characters enclosed in curly braces,
22658
+ * like {foo} or {?bar}.
22659
+ */
22660
+ static isTemplate(str) {
22661
+ return /\{[^}\s]+\}/.test(str);
22662
+ }
22663
+ static validateLength(str, max, context) {
22664
+ if (str.length > max) {
22665
+ throw new Error(`${context} exceeds maximum length of ${max} characters (got ${str.length})`);
22666
+ }
22667
+ }
22668
+ get variableNames() {
22669
+ return this.parts.flatMap((part) => typeof part === "string" ? [] : part.names);
22670
+ }
22671
+ constructor(template) {
22672
+ _UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, "Template");
22673
+ this.template = template;
22674
+ this.parts = this.parse(template);
22675
+ }
22676
+ toString() {
22677
+ return this.template;
22678
+ }
22679
+ parse(template) {
22680
+ const parts = [];
22681
+ let currentText = "";
22682
+ let i = 0;
22683
+ let expressionCount = 0;
22684
+ while (i < template.length) {
22685
+ if (template[i] === "{") {
22686
+ if (currentText) {
22687
+ parts.push(currentText);
22688
+ currentText = "";
22689
+ }
22690
+ const end = template.indexOf("}", i);
22691
+ if (end === -1)
22692
+ throw new Error("Unclosed template expression");
22693
+ expressionCount++;
22694
+ if (expressionCount > MAX_TEMPLATE_EXPRESSIONS) {
22695
+ throw new Error(`Template contains too many expressions (max ${MAX_TEMPLATE_EXPRESSIONS})`);
22696
+ }
22697
+ const expr = template.slice(i + 1, end);
22698
+ const operator = this.getOperator(expr);
22699
+ const exploded = expr.includes("*");
22700
+ const names = this.getNames(expr);
22701
+ const name = names[0];
22702
+ for (const name2 of names) {
22703
+ _UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
22704
+ }
22705
+ parts.push({ name, operator, names, exploded });
22706
+ i = end + 1;
22707
+ } else {
22708
+ currentText += template[i];
22709
+ i++;
22710
+ }
22711
+ }
22712
+ if (currentText) {
22713
+ parts.push(currentText);
22714
+ }
22715
+ return parts;
22716
+ }
22717
+ getOperator(expr) {
22718
+ const operators = ["+", "#", ".", "/", "?", "&"];
22719
+ return operators.find((op) => expr.startsWith(op)) || "";
22720
+ }
22721
+ getNames(expr) {
22722
+ const operator = this.getOperator(expr);
22723
+ return expr.slice(operator.length).split(",").map((name) => name.replace("*", "").trim()).filter((name) => name.length > 0);
22724
+ }
22725
+ encodeValue(value, operator) {
22726
+ _UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, "Variable value");
22727
+ if (operator === "+" || operator === "#") {
22728
+ return encodeURI(value);
22729
+ }
22730
+ return encodeURIComponent(value);
22731
+ }
22732
+ expandPart(part, variables) {
22733
+ if (part.operator === "?" || part.operator === "&") {
22734
+ const pairs = part.names.map((name) => {
22735
+ const value2 = variables[name];
22736
+ if (value2 === void 0)
22737
+ return "";
22738
+ const encoded2 = Array.isArray(value2) ? value2.map((v) => this.encodeValue(v, part.operator)).join(",") : this.encodeValue(value2.toString(), part.operator);
22739
+ return `${name}=${encoded2}`;
22740
+ }).filter((pair) => pair.length > 0);
22741
+ if (pairs.length === 0)
22742
+ return "";
22743
+ const separator = part.operator === "?" ? "?" : "&";
22744
+ return separator + pairs.join("&");
22745
+ }
22746
+ if (part.names.length > 1) {
22747
+ const values2 = part.names.map((name) => variables[name]).filter((v) => v !== void 0);
22748
+ if (values2.length === 0)
22749
+ return "";
22750
+ return values2.map((v) => Array.isArray(v) ? v[0] : v).join(",");
22751
+ }
22752
+ const value = variables[part.name];
22753
+ if (value === void 0)
22754
+ return "";
22755
+ const values = Array.isArray(value) ? value : [value];
22756
+ const encoded = values.map((v) => this.encodeValue(v, part.operator));
22757
+ switch (part.operator) {
22758
+ case "":
22759
+ return encoded.join(",");
22760
+ case "+":
22761
+ return encoded.join(",");
22762
+ case "#":
22763
+ return "#" + encoded.join(",");
22764
+ case ".":
22765
+ return "." + encoded.join(".");
22766
+ case "/":
22767
+ return "/" + encoded.join("/");
22768
+ default:
22769
+ return encoded.join(",");
22770
+ }
22771
+ }
22772
+ expand(variables) {
22773
+ let result = "";
22774
+ let hasQueryParam = false;
22775
+ for (const part of this.parts) {
22776
+ if (typeof part === "string") {
22777
+ result += part;
22778
+ continue;
22779
+ }
22780
+ const expanded = this.expandPart(part, variables);
22781
+ if (!expanded)
22782
+ continue;
22783
+ if ((part.operator === "?" || part.operator === "&") && hasQueryParam) {
22784
+ result += expanded.replace("?", "&");
22785
+ } else {
22786
+ result += expanded;
22787
+ }
22788
+ if (part.operator === "?" || part.operator === "&") {
22789
+ hasQueryParam = true;
22790
+ }
22791
+ }
22792
+ return result;
22793
+ }
22794
+ escapeRegExp(str) {
22795
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22796
+ }
22797
+ partToRegExp(part) {
22798
+ const patterns = [];
22799
+ for (const name2 of part.names) {
22800
+ _UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
22801
+ }
22802
+ if (part.operator === "?" || part.operator === "&") {
22803
+ for (let i = 0; i < part.names.length; i++) {
22804
+ const name2 = part.names[i];
22805
+ const prefix = i === 0 ? "\\" + part.operator : "&";
22806
+ patterns.push({
22807
+ pattern: prefix + this.escapeRegExp(name2) + "=([^&]+)",
22808
+ name: name2
22809
+ });
22810
+ }
22811
+ return patterns;
22812
+ }
22813
+ let pattern;
22814
+ const name = part.name;
22815
+ switch (part.operator) {
22816
+ case "":
22817
+ pattern = part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)";
22818
+ break;
22819
+ case "+":
22820
+ case "#":
22821
+ pattern = "(.+)";
22822
+ break;
22823
+ case ".":
22824
+ pattern = "\\.([^/,]+)";
22825
+ break;
22826
+ case "/":
22827
+ pattern = "/" + (part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)");
22828
+ break;
22829
+ default:
22830
+ pattern = "([^/]+)";
22831
+ }
22832
+ patterns.push({ pattern, name });
22833
+ return patterns;
22834
+ }
22835
+ match(uri) {
22836
+ _UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, "URI");
22837
+ let pattern = "^";
22838
+ const names = [];
22839
+ for (const part of this.parts) {
22840
+ if (typeof part === "string") {
22841
+ pattern += this.escapeRegExp(part);
22842
+ } else {
22843
+ const patterns = this.partToRegExp(part);
22844
+ for (const { pattern: partPattern, name } of patterns) {
22845
+ pattern += partPattern;
22846
+ names.push({ name, exploded: part.exploded });
22847
+ }
22848
+ }
22849
+ }
22850
+ pattern += "$";
22851
+ _UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, "Generated regex pattern");
22852
+ const regex = new RegExp(pattern);
22853
+ const match = uri.match(regex);
22854
+ if (!match)
22855
+ return null;
22856
+ const result = {};
22857
+ for (let i = 0; i < names.length; i++) {
22858
+ const { name, exploded } = names[i];
22859
+ const value = match[i + 1];
22860
+ const cleanName = name.replace("*", "");
22861
+ if (exploded && value.includes(",")) {
22862
+ result[cleanName] = value.split(",");
22863
+ } else {
22864
+ result[cleanName] = value;
22865
+ }
22866
+ }
22867
+ return result;
22868
+ }
22869
+ };
22870
+
22649
22871
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
22650
22872
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
22651
22873
  function validateToolName(name) {
@@ -23475,6 +23697,30 @@ var McpServer = class {
23475
23697
  }
23476
23698
  }
23477
23699
  };
23700
+ var ResourceTemplate = class {
23701
+ constructor(uriTemplate, _callbacks) {
23702
+ this._callbacks = _callbacks;
23703
+ this._uriTemplate = typeof uriTemplate === "string" ? new UriTemplate(uriTemplate) : uriTemplate;
23704
+ }
23705
+ /**
23706
+ * Gets the URI template pattern.
23707
+ */
23708
+ get uriTemplate() {
23709
+ return this._uriTemplate;
23710
+ }
23711
+ /**
23712
+ * Gets the list callback, if one was provided.
23713
+ */
23714
+ get listCallback() {
23715
+ return this._callbacks.list;
23716
+ }
23717
+ /**
23718
+ * Gets the callback for completing a specific URI template variable, if one was provided.
23719
+ */
23720
+ completeCallback(variable) {
23721
+ return this._callbacks.complete?.[variable];
23722
+ }
23723
+ };
23478
23724
  var EMPTY_OBJECT_JSON_SCHEMA = {
23479
23725
  type: "object",
23480
23726
  properties: {}
@@ -23551,6 +23797,68 @@ var EMPTY_COMPLETION_RESULT = {
23551
23797
  }
23552
23798
  };
23553
23799
 
23800
+ // src/prompts.ts
23801
+ var TASK_ARG = external_exports.string().min(1).describe("\u4EFB\u52A1\u7F16\u53F7\uFF0C\u5982 MOM-12");
23802
+ var user = (lines) => [
23803
+ { role: "user", content: { type: "text", text: lines.filter((l) => typeof l === "string").join("\n") } }
23804
+ ];
23805
+ function createPrompts(profile = "work") {
23806
+ const canDigest = profile === "full";
23807
+ return [
23808
+ {
23809
+ name: "start_task",
23810
+ title: "\u5F00\u59CB\u505A\u4EFB\u52A1",
23811
+ description: "\u9886\u53D6\u4E00\u4E2A Momentum \u4EFB\u52A1\u5E76\u6309\u56E2\u961F\u6D41\u7A0B\u5F00\u5DE5\uFF1A\u8BFB\u4E0A\u4E0B\u6587\u3001\u8BB2\u6E05\u65B9\u6848\u3001\u52A8\u624B\u3001\u5173\u952E\u8282\u70B9\u7559\u8BC4\u8BBA\u3002",
23812
+ argsSchema: { task: TASK_ARG, note: external_exports.string().optional().describe("\u8FD9\u6B21\u8981\u505A\u4EC0\u4E48\u3001\u6709\u4EC0\u4E48\u989D\u5916\u8981\u6C42") },
23813
+ render: ({ task, note }) => user([
23814
+ `\u8BF7\u5F00\u59CB\u505A Momentum \u4EFB\u52A1 ${task}\u3002`,
23815
+ "",
23816
+ `1. \u76F4\u63A5\u8C03 begin_task("${task}")\uFF1A\u5B83\u4E00\u6B21\u5B8C\u6210\u89E3\u6790\u7F16\u53F7\u3001\u5F00\u8FD0\u884C\u3001\u9886\u53D6\u5E76\u8FD4\u56DE\u7D27\u51D1\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u5FC5\u5148 whoami / get_task\u3002\u5FC3\u8DF3\u81EA\u52A8\u7EED\u7EA6\u3002`,
23817
+ "2. \u8BFB\u4E0A\u4E0B\u6587\uFF1A\u63CF\u8FF0\u3001\u9A8C\u6536\u6807\u51C6\uFF08\u8BB0\u4E0B specVersion\uFF0C\u4EA4\u4ED8\u65F6\u8981\u7528\uFF09\u3001\u4F9D\u8D56\u3001\u5DF2\u6709\u51B3\u5B9A\u3001\u5F85\u7B54\u95EE\u9898\u548C\u6700\u65B0\u4EA4\u4ED8\u3002\u88AB\u4F9D\u8D56\u5361\u4F4F\u3001\u6216\u6709\u963B\u585E\u95EE\u9898\u6CA1\u7B54\u65F6\uFF0C\u5148\u505C\u4E0B\u6765\u544A\u8BC9\u6211\u3002",
23818
+ "3. \u52A8\u624B\u524D\u5148\u7528\u5927\u767D\u8BDD\u8BB2\u6E05\u6253\u7B97\u600E\u4E48\u505A\u3001\u6BCF\u6761\u9A8C\u6536\u6807\u51C6\u6253\u7B97\u600E\u4E48\u9A8C\u8BC1\uFF1B\u65B9\u6848\u503C\u5F97\u7559\u6863\u5C31\u7528 update_task_plan \u5199\u8FDB\u6267\u884C\u7EC6\u5316\u3002",
23819
+ "4. \u5F00\u59CB\u5E72\u6D3B\u3002\u7F3A\u4FE1\u606F\u5148\u95EE\u6211\uFF1B\u6211\u7B54\u4E0D\u4E86\u3001\u8981\u56E2\u961F\u62CD\u677F\u7684\u624D\u7528 ask_question\u3002\u5173\u952E\u8282\u70B9\u7528 comment_on_task \u5199\u4E00\u6761\u77ED\u8FDB\u5C55\uFF08\u5148\u7ED3\u8BBA\u3001\u518D\u8981\u70B9\uFF09\u3002",
23820
+ "5. \u505A\u5B8C\u6309\u300C\u5199\u4EA4\u4ED8\u8BF4\u660E\u300D\uFF08write_delivery\uFF09\u7684\u8981\u6C42\u8C03 finish_task_work \u63D0\u4EA4\u4EA4\u4ED8\u5E76\u79FB\u5230\u8BC4\u5BA1\u3002",
23821
+ note && "",
23822
+ note && `\u8865\u5145\u8981\u6C42\uFF1A${note}`
23823
+ ])
23824
+ },
23825
+ {
23826
+ name: "digest_discussion",
23827
+ title: "\u6574\u7406\u8BA8\u8BBA\u6210\u63D0\u6848",
23828
+ description: "\u628A\u5F53\u524D\u4F1A\u8BDD\u91CC\u7684\u8BA8\u8BBA\u6574\u7406\u6210\u4EFB\u52A1\u7684\u4FEE\u6539\u63D0\u6848\uFF1A\u62CD\u677F\u7684\u51B3\u5B9A\u3001\u5F85\u7B54\u95EE\u9898\u3001\u8BF4\u660E\u4E0E\u9A8C\u6536\u6807\u51C6\u7684\u6539\u52A8\u3002",
23829
+ argsSchema: { task: TASK_ARG, focus: external_exports.string().optional().describe("\u53EA\u6574\u7406\u8BA8\u8BBA\u91CC\u7684\u54EA\u4E00\u90E8\u5206") },
23830
+ render: ({ task, focus }) => user([
23831
+ `\u628A\u6211\u4EEC\u8FD9\u6BB5\u8BA8\u8BBA\u6574\u7406\u6210\u4EFB\u52A1 ${task} \u7684\u4FEE\u6539\u63D0\u6848\u3002`,
23832
+ focus && `\u53EA\u6574\u7406\u8FD9\u90E8\u5206\uFF1A${focus}`,
23833
+ "",
23834
+ `1. \u5148\u8C03 get_task_context("${task}") \u770B\u5F53\u524D\u8BF4\u660E\u3001\u7248\u672C\u53F7 specVersion\u3001\u5DF2\u6709\u51B3\u5B9A\u548C\u5F85\u7B54\u95EE\u9898\uFF0C\u5DF2\u6709\u7684\u4E0D\u8981\u91CD\u590D\u3002`,
23835
+ "2. \u4ECE\u8BA8\u8BBA\u91CC\u63D0\u53D6\u4E09\u7C7B\u4E1C\u897F\uFF1A",
23836
+ " - \u5DF2\u7ECF\u62CD\u677F\u7684\u51B3\u5B9A\uFF1A\u4E00\u6761\u53EA\u8BF4\u4E00\u4EF6\u4E8B\uFF0C\u5199\u6E05\u7406\u7531\uFF1B",
23837
+ " - \u8FD8\u6CA1\u6709\u7B54\u6848\u7684\u95EE\u9898\uFF1A\u6807\u51FA\u54EA\u4E9B\u4F1A\u5361\u4F4F\u8FDB\u5EA6\uFF1B",
23838
+ " - \u63CF\u8FF0\u6216\u9A8C\u6536\u6807\u51C6\u8981\u600E\u4E48\u6539\uFF1A\u9A8C\u6536\u6807\u51C6\u6BCF\u6761\u53EF\u6838\u5BF9\uFF0C\u4FDD\u7559\u539F\u6709\u7684 id\u3002",
23839
+ " \u8BA8\u8BBA\u4E2D\u7684\u8BBE\u60F3\u3001\u53EA\u6709\u6211\u4E00\u65B9\u63D0\u8FC7\u7684\u60F3\u6CD5\u4E0D\u7B97\u51B3\u5B9A\uFF0C\u4E0D\u80FD\u81EA\u884C\u8BA4\u5B9A\u3002",
23840
+ "3. \u5148\u628A\u8349\u7A3F\u7528\u5927\u767D\u8BDD\u5217\u7ED9\u6211\u8FC7\u76EE\uFF0C\u6211\u786E\u8BA4\u540E\u518D\u63D0\u4EA4\u3002",
23841
+ canDigest ? "4. \u786E\u8BA4\u540E\u8C03 propose_digest\uFF0Cspec_change.expected_version \u586B\u7B2C 1 \u6B65\u8BFB\u5230\u7684 specVersion\u3002\u5B83\u662F\u4E00\u6761\u5F85\u4EBA\u786E\u8BA4\u7684\u63D0\u6848\uFF1A\u63A5\u53D7\u540E\u4E00\u6B21\u751F\u6548\u3001\u8BF4\u660E\u5F62\u6210\u65B0\u7248\u672C\uFF0C\u5728\u90A3\u4E4B\u524D\u4EFB\u52A1\u6CA1\u6709\u4EFB\u4F55\u6539\u52A8\u3002" : "4. \u5F53\u524D\u5DE5\u5177\u6863\uFF08work\uFF09\u6CA1\u6709 propose_digest\uFF1A\u786E\u8BA4\u540E\u628A\u51B3\u5B9A\u548C\u8BF4\u660E\u6539\u52A8\u7528 comment_on_task \u8BB0\u5230\u4EFB\u52A1\u4E0B\uFF0C\u963B\u585E\u95EE\u9898\u7528 ask_question \u63D0\u51FA\u6765\uFF1B\u5E76\u63D0\u9192\u6211\u8BF4\u660E\u672C\u8EAB\u8981\u5728 Momentum \u754C\u9762\u91CC\u6539\uFF0C\u6216\u628A MOMENTUM_TOOL_PROFILE \u8BBE\u6210 full \u540E\u518D\u63D0\u4EA4\u63D0\u6848\u3002"
23842
+ ])
23843
+ },
23844
+ {
23845
+ name: "write_delivery",
23846
+ title: "\u5199\u4EA4\u4ED8\u8BF4\u660E",
23847
+ description: "\u6309\u4EFB\u52A1\u8BF4\u660E\u9010\u6761\u8986\u76D6\u9A8C\u6536\u6807\u51C6\uFF0C\u5199\u597D\u4EA4\u4ED8\u8BF4\u660E\u5E76\u8C03 finish_task_work \u63D0\u4EA4\u3001\u79FB\u5230\u8BC4\u5BA1\u3002",
23848
+ argsSchema: { task: TASK_ARG },
23849
+ render: ({ task }) => user([
23850
+ `\u4E3A\u4EFB\u52A1 ${task} \u5199\u4EA4\u4ED8\u8BF4\u660E\u5E76\u63D0\u4EA4\u3002`,
23851
+ "",
23852
+ `1. \u624B\u4E0A\u6CA1\u6709\u9A8C\u6536\u6807\u51C6\u548C specVersion \u65F6\u5148\u8C03 get_task_context("${task}")\u3002based_on_spec_version \u586B\u4F60\u5B9E\u9645\u5BF9\u7167\u7684\u90A3\u4E2A\u7248\u672C\uFF1B\u5F00\u5DE5\u540E\u8BF4\u660E\u5347\u8FC7\u7248\uFF0C\u5148\u5BF9\u7167\u65B0\u7248\u672C\u770B\u8FD8\u5DEE\u4EC0\u4E48\u3002`,
23853
+ "2. summary \u5199\u7ED9\u4EFB\u52A1\u6307\u6D3E\u7684\u4EBA\u770B\uFF1A\u5927\u767D\u8BDD\u8BB2\u505A\u4E86\u4EC0\u4E48\u3001\u5173\u952E\u53D6\u820D\u3001\u5BF9\u4F7F\u7528\u8005\u7684\u5F71\u54CD\uFF1B\u6D41\u7A0B\u6216\u6A21\u5757\u5173\u7CFB\u7528 mermaid \u753B\u51FA\u6765\u3002\u5DE5\u7A0B\u5E08\u591A\u8BB2\u5B9E\u73B0\u53D6\u820D\uFF0C\u5176\u4ED6\u4EBA\u53EA\u8BB2\u7ED3\u679C\u3002",
23854
+ "3. results \u9010\u6761\u8986\u76D6\u6BCF\u4E2A\u9A8C\u6536\u6807\u51C6\uFF08criterionId \u7528\u8BF4\u660E\u91CC\u7684 id\uFF09\uFF1Aoutcome \u53EA\u7528 passed / failed / unverified\uFF0Cevidence \u5199\u522B\u4EBA\u80FD\u6838\u5BF9\u7684\u8BC1\u636E\uFF08\u6D4B\u8BD5\u540D\u3001\u547D\u4EE4\u8F93\u51FA\u3001\u94FE\u63A5\uFF09\u3002\u6CA1\u6709\u771F\u6B63\u9A8C\u8BC1\u8FC7\u7684\u6807 unverified\uFF0C\u4E0D\u8981\u731C\u3002",
23855
+ "4. artifacts \u653E PR\u3001\u6587\u6863\u7B49\u94FE\u63A5\uFF1B\u6CA1\u505A\u5B8C\u7684\u5199\u8FDB remaining_work\uFF1B\u63A5\u624B\u7684\u4EBA\u9700\u8981\u77E5\u9053\u7684\u5199\u8FDB handoff\u3002",
23856
+ "5. \u8C03 finish_task_work \u63D0\u4EA4\uFF1Aidempotency_key \u53D6\u4E00\u4E2A\u7A33\u5B9A\u7684\u503C\uFF0C\u67D0\u4E00\u6B65\u5931\u8D25\u65F6\u7528\u76F8\u540C\u53C2\u6570\u91CD\u8BD5\u3002\u4EFB\u52A1\u4F1A\u8FDB\u8BC4\u5BA1\uFF0C\u8BC4\u5BA1\u901A\u8FC7\u524D\u4E0D\u7B97\u5B8C\u6210\u3002"
23857
+ ])
23858
+ }
23859
+ ];
23860
+ }
23861
+
23554
23862
  // ../../packages/core/src/task-schemas.ts
23555
23863
  var markdownSchema = external_exports.string().max(2e5);
23556
23864
  var httpUrl = external_exports.string().url().max(2e3).refine((url) => /^https?:\/\//i.test(url), "\u53EA\u652F\u6301 HTTP(S) \u94FE\u63A5");
@@ -23601,11 +23909,13 @@ var RunHeartbeats = class {
23601
23909
  lastRunOfTask = /* @__PURE__ */ new Map();
23602
23910
  pending = [];
23603
23911
  timer = null;
23912
+ closed = false;
23604
23913
  intervalMs;
23605
23914
  leaseSeconds;
23606
23915
  log;
23607
23916
  /** 开始替这次运行续约。expectClaim = 它手上应当有领取,续不上要告诉模型 */
23608
23917
  track(runId, taskId, expectClaim = false) {
23918
+ if (this.closed) return;
23609
23919
  this.runs.set(runId, { taskId, expectClaim });
23610
23920
  if (taskId) this.lastRunOfTask.set(taskId, runId);
23611
23921
  if (!this.timer) {
@@ -23628,6 +23938,10 @@ var RunHeartbeats = class {
23628
23938
  has(runId) {
23629
23939
  return this.runs.has(runId);
23630
23940
  }
23941
+ /** 正在续约的运行数 */
23942
+ get size() {
23943
+ return this.runs.size;
23944
+ }
23631
23945
  /** 取走攒下的提醒。每条只给模型看一次 */
23632
23946
  drainNotices() {
23633
23947
  return this.pending.splice(0);
@@ -23636,6 +23950,15 @@ var RunHeartbeats = class {
23636
23950
  if (this.timer) clearInterval(this.timer);
23637
23951
  this.timer = null;
23638
23952
  }
23953
+ /**
23954
+ * 永久停掉:之后再 track 也不会续约。远程会话(http.ts)结束时用 —— 会话关掉的那一刻
23955
+ * 可能还有一个 begin_task 在路上,它回来时的 track 不能把一个已经没人的会话重新续活
23956
+ */
23957
+ close() {
23958
+ this.closed = true;
23959
+ this.runs.clear();
23960
+ this.stop();
23961
+ }
23639
23962
  /** 给所有在跟的运行各发一次心跳。定时器调它,测试也直接调它 */
23640
23963
  async tick() {
23641
23964
  await Promise.all([...this.runs].map(([runId, run2]) => this.beatOne(runId, run2)));
@@ -23977,6 +24300,14 @@ var BEGIN_OUTPUT = external_exports.object({
23977
24300
  context: CONTEXT_SCHEMA.optional(),
23978
24301
  proposalId: external_exports.string().optional()
23979
24302
  }).passthrough();
24303
+ var NAMED = external_exports.object({ id: external_exports.string(), name: external_exports.string() }).passthrough();
24304
+ var WHOAMI_OUTPUT = external_exports.object({
24305
+ kind: external_exports.enum(["agent", "human"]),
24306
+ actorId: external_exports.string().nullable(),
24307
+ orgId: external_exports.string().nullable(),
24308
+ agent: NAMED.optional(),
24309
+ instance: NAMED.optional()
24310
+ }).passthrough();
23980
24311
  var PAGE_OUTPUT = external_exports.object({ items: external_exports.array(external_exports.unknown()) }).passthrough();
23981
24312
  var TASKS_OUTPUT = external_exports.object({ items: external_exports.array(external_exports.object(TASK_FIELDS).passthrough()) }).passthrough();
23982
24313
  var BROWSE_OUTCOME = external_exports.enum(["ok", "no_projects", "no_match", "project_unavailable", "failed"]);
@@ -23986,7 +24317,7 @@ var BROWSE_OUTPUT = external_exports.object({
23986
24317
  items: external_exports.array(external_exports.unknown()),
23987
24318
  nextOffset: external_exports.number().nullable()
23988
24319
  }).passthrough();
23989
- var READ = { readOnlyHint: true, openWorldHint: false };
24320
+ var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
23990
24321
  var write = (destructiveHint, idempotentHint) => ({
23991
24322
  readOnlyHint: false,
23992
24323
  destructiveHint,
@@ -24010,7 +24341,7 @@ var TOOL_META = {
24010
24341
  list_task_deliveries: { annotations: READ, outputSchema: PAGE_OUTPUT },
24011
24342
  poll_events: { annotations: READ, outputSchema: PAGE_OUTPUT },
24012
24343
  current_cycle: { annotations: READ },
24013
- whoami: { annotations: READ },
24344
+ whoami: { annotations: READ, outputSchema: WHOAMI_OUTPUT },
24014
24345
  // 开工 / 收尾:重复调用沿用同一次运行、交付按 idempotency_key 去重
24015
24346
  begin_task: { annotations: write(false, true), outputSchema: BEGIN_OUTPUT },
24016
24347
  finish_task_work: { annotations: write(false, true), outputSchema: FINISH_OUTPUT },
@@ -25381,6 +25712,52 @@ function explain(err) {
25381
25712
  return `\u5931\u8D25\uFF1A${message}`;
25382
25713
  }
25383
25714
 
25715
+ // src/resources.ts
25716
+ var SPEC_URI = (key, version2) => `momentum://task/${encodeURIComponent(key)}/spec${version2 === void 0 ? "" : `/${version2}`}`;
25717
+ var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25718
+ function renderSpec(key, spec, current) {
25719
+ const at = spec.createdAt instanceof Date ? spec.createdAt.toISOString() : spec.createdAt;
25720
+ const criteria = spec.acceptance?.criteria ?? [];
25721
+ return [
25722
+ `# ${key} \xB7 ${spec.title}`,
25723
+ "",
25724
+ `\u8BF4\u660E\u7248\u672C v${spec.version}\uFF08\u4E0D\u53EF\u53D8\uFF0C${current ? "\u5F53\u524D\u7248\uFF0C" : ""}\u5730\u5740 ${SPEC_URI(key, spec.version)}\uFF09\xB7 ${at}`,
25725
+ ...spec.changeSummary ? [`\u672C\u7248\u53D8\u66F4\uFF1A${spec.changeSummary}`] : [],
25726
+ "",
25727
+ "## \u63CF\u8FF0",
25728
+ "",
25729
+ spec.descriptionMd?.trim() || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
25730
+ "",
25731
+ "## \u9A8C\u6536\u6807\u51C6",
25732
+ "",
25733
+ ...criteria.length ? criteria.map((c) => `- [${c.id}] ${c.text}`) : ["\uFF08\u672A\u5B9A\u4E49\u9A8C\u6536\u6807\u51C6\uFF09"]
25734
+ ].join("\n");
25735
+ }
25736
+ function createSpecReader(client) {
25737
+ async function resolve(ref) {
25738
+ const [found] = UUID2.test(ref) ? [await client.task.ref.query({ taskId: ref })] : await client.task.list.query({ key: ref, limit: 1 });
25739
+ if (!found) throw new Error(`\u627E\u4E0D\u5230\u4EFB\u52A1 '${ref}'`);
25740
+ return { id: found.id, key: found.key };
25741
+ }
25742
+ return async function readSpec(ref, version2) {
25743
+ if (version2 !== void 0 && !/^\d+$/.test(version2)) {
25744
+ throw new Error(`\u8BF4\u660E\u7248\u672C\u53F7\u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570\uFF0C\u6536\u5230 '${version2}'`);
25745
+ }
25746
+ try {
25747
+ const task = await resolve(ref);
25748
+ const current = (await client.task.context.query({ taskId: task.id })).specVersion;
25749
+ const wanted = version2 === void 0 ? current : Number(version2);
25750
+ const spec = await client.task.spec.query({ taskId: task.id, version: wanted });
25751
+ return {
25752
+ mimeType: "text/markdown",
25753
+ text: renderSpec(task.key, spec, wanted === current)
25754
+ };
25755
+ } catch (err) {
25756
+ throw new Error(explain(err));
25757
+ }
25758
+ };
25759
+ }
25760
+
25384
25761
  // src/server.ts
25385
25762
  var SERVER_INFO = {
25386
25763
  name: "momentum",
@@ -25426,6 +25803,29 @@ function createMcpServer(client, profile = "work", options = {}) {
25426
25803
  (args, extra) => tool.handler(args, extra._meta)
25427
25804
  );
25428
25805
  }
25806
+ for (const prompt of createPrompts(profile)) {
25807
+ server.registerPrompt(
25808
+ prompt.name,
25809
+ { title: prompt.title, description: prompt.description, argsSchema: prompt.argsSchema },
25810
+ (args) => ({ description: prompt.description, messages: prompt.render(args) })
25811
+ );
25812
+ }
25813
+ const readSpec = createSpecReader(client);
25814
+ const one = (value) => Array.isArray(value) ? value[0] ?? "" : value ?? "";
25815
+ server.registerResource(
25816
+ "task_spec",
25817
+ new ResourceTemplate("momentum://task/{key}/spec", { list: void 0 }),
25818
+ { title: "\u4EFB\u52A1\u8BF4\u660E\uFF08\u5F53\u524D\u7248\uFF09", description: "\u4EFB\u52A1\u5F53\u524D\u7248\u672C\u7684\u6807\u9898\u3001\u63CF\u8FF0\u548C\u9A8C\u6536\u6807\u51C6\uFF0C\u6B63\u6587\u91CC\u5199\u660E\u7248\u672C\u53F7\u548C\u90A3\u4E00\u7248\u7684\u4E0D\u53EF\u53D8\u5730\u5740\u3002", mimeType: "text/markdown" },
25819
+ async (uri, vars) => ({ contents: [{ uri: uri.href, ...await readSpec(decodeURIComponent(one(vars.key))) }] })
25820
+ );
25821
+ server.registerResource(
25822
+ "task_spec_version",
25823
+ new ResourceTemplate("momentum://task/{key}/spec/{version}", { list: void 0 }),
25824
+ { title: "\u4EFB\u52A1\u8BF4\u660E\uFF08\u6307\u5B9A\u7248\u672C\uFF09", description: "\u4EFB\u52A1\u8BF4\u660E\u7684\u67D0\u4E00\u7248\uFF0C\u5185\u5BB9\u4E0D\u53EF\u53D8\u3002\u4EA4\u4ED8\u65F6 based_on_spec_version \u5BF9\u7167\u7684\u5C31\u662F\u5B83\u3002", mimeType: "text/markdown" },
25825
+ async (uri, vars) => ({
25826
+ contents: [{ uri: uri.href, ...await readSpec(decodeURIComponent(one(vars.key)), decodeURIComponent(one(vars.version))) }]
25827
+ })
25828
+ );
25429
25829
  return server;
25430
25830
  }
25431
25831
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@costgate/momentum-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Momentum MCP Server —— 把 Momentum 的任务、领取与交付接进任意 MCP 客户端",
5
5
  "type": "module",
6
6
  "bin": {