argus-decision-mcp 1.9.0 → 1.11.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.
@@ -166,7 +166,11 @@ async function fire(dir, todayOverride) {
166
166
  ? `Argus: 확인일이 지난 예측이 있어요. "${text}" (확인일 ${first.date}). 현실이 어떻게 답했나요? 지금 어려우면 닫아도 됩니다. 다시 조르지 않아요.`
167
167
  : `Argus: a prediction passed its check-by. "${text}" (due ${first.date}). What did reality do? Dismiss if now is a bad time; no re-asking.`, {
168
168
  type: 'object',
169
- required: ['outcome'],
169
+ // 필수 필드 없음 (2026-07-27) — 필수 enum은 호스트가 접어서 렌더하고
170
+ // (펼치기 키가 하나 더 붙는다) 빈 Accept를 폼 안에서 빨갛게 막는다.
171
+ // 여기선 특히 나쁘다: 이건 사용자가 부르지도 않았는데 뜨는 선제
172
+ // 픽커라, 마찰 탈출구가 살아 있어야 한다. 빈 Accept는 아래에서
173
+ // 거절과 같은 길로 흘러 아무것도 쓰지 않는다 — 정직한 공백.
170
174
  properties: {
171
175
  outcome: {
172
176
  type: 'string',
@@ -193,7 +197,8 @@ async function fire(dir, todayOverride) {
193
197
  ? '실제로 무슨 일이 있었나요? 한 줄이면 됩니다. 당신의 말 그대로 기록됩니다.'
194
198
  : 'What actually happened, in one line? Recorded verbatim, in your words.', {
195
199
  type: 'object',
196
- required: ['what_happened'],
200
+ // 필수 필드 없음 — 같은 이유. 비우고 Accept하면 아래에서
201
+ // 기록하지 않는다(날조 금지). 폼이 막을 일이 아니다.
197
202
  properties: {
198
203
  what_happened: {
199
204
  type: 'string', maxLength: 600,
@@ -8,6 +8,7 @@ import { z } from 'zod';
8
8
  import { tunedStandingSense } from '../lib/ambient-prefs.js';
9
9
  import { envelope } from '../lib/envelope.js';
10
10
  import { canElicit } from '../lib/elicit.js';
11
+ import { packageMeta } from '../lib/package-meta.js';
11
12
  import { ENVELOPE_OUTPUT_SCHEMA, zArgusDir, zDate } from './tool-types.js';
12
13
  import { handleToolException } from './errors.js';
13
14
  import { briefDivergence, readV2Brief } from '../v2/mirror.js';
@@ -28,6 +29,19 @@ const anchorMirrorShownFor = new Set();
28
29
  export function resetCheckInSession() {
29
30
  anchorMirrorShownFor.clear();
30
31
  }
32
+ /**
33
+ * The wire facts a session can see about ITSELF. `picker` answers "does this host
34
+ * show real pickers"; `server_version` answers "which build am I actually talking
35
+ * to" — the gap that let a founder dogfood 1.2.0 for twelve days while seven
36
+ * releases sat on npm (npx reuses a cached install whenever the spec is a RANGE,
37
+ * so `argus-decision-mcp@^1` never upgrades on its own). CI gates the repo and
38
+ * npm holds the latest; neither can see what a live session actually launched.
39
+ * Reported on every check_in so `/doctor` — and the user — can compare it to the
40
+ * version the plugin pins, instead of inferring staleness from missing behavior.
41
+ */
42
+ function wireFacts() {
43
+ return { picker: canElicit() ? 'one_tap' : 'text_fallback', server_version: packageMeta().version };
44
+ }
31
45
  const inputSchema = z.strictObject({
32
46
  argus_dir: zArgusDir,
33
47
  // max(365), not max(30): the handler clamps the WINDOW to 30 days, but the
@@ -272,14 +286,14 @@ export const checkIn = {
272
286
  ok: true, tool: 'argus_check_in',
273
287
  surface: S.first_run,
274
288
  next_actions: ['argus_capture'],
275
- data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, first_run: true, today, picker: canElicit() ? 'one_tap' : 'text_fallback' },
289
+ data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, first_run: true, today, ...wireFacts() },
276
290
  });
277
291
  }
278
292
  return envelope({
279
293
  ok: true, tool: 'argus_check_in',
280
294
  surface: mirrorLine + S.nothing_due + accountHint + upcomingLine + fleetLine + integrityLine,
281
295
  next_actions: ['stop'],
282
- data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, picker: canElicit() ? 'one_tap' : 'text_fallback', ...(openWatch.length ? { open_predictions: openWatch, standing_sense: tunedStandingSense() } : {}), ...(upDays > 0 ? { upcoming } : {}), ...(a['fleet'] === true ? { fleet: fleetRows } : {}), ...watchData, today, integrity: ledger.integrity, ...(process.env['ARGUS_V2_DEBUG'] === '1' ? { capture_status: captureStatus, v2_brief: readV2Brief(dir, today), v2_divergence: briefDivergence([], readV2Brief(dir, today)) } : {}) },
296
+ data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, ...wireFacts(), ...(openWatch.length ? { open_predictions: openWatch, standing_sense: tunedStandingSense() } : {}), ...(upDays > 0 ? { upcoming } : {}), ...(a['fleet'] === true ? { fleet: fleetRows } : {}), ...watchData, today, integrity: ledger.integrity, ...(process.env['ARGUS_V2_DEBUG'] === '1' ? { capture_status: captureStatus, v2_brief: readV2Brief(dir, today), v2_divergence: briefDivergence([], readV2Brief(dir, today)) } : {}) },
283
297
  });
284
298
  }
285
299
  const parts = [];
@@ -313,7 +327,7 @@ export const checkIn = {
313
327
  surface: mirrorLine + parts.join(' ') + upcomingLine + fleetLine + integrityLine,
314
328
  next_actions: next,
315
329
  data: {
316
- picker: canElicit() ? 'one_tap' : 'text_fallback',
330
+ ...wireFacts(),
317
331
  due: dueEnriched, due_count: dueAll.length,
318
332
  ...(dueTruncated > 0 ? { due_truncated: `${dueAll.length} due, showing ${DUE_TOP} oldest` } : {}),
319
333
  due_premises: duePrem, due_premise_count: premiseGroups.length,
@@ -444,7 +444,10 @@ async function opResolve(dir, id, now, state, existing, a) {
444
444
  const qLocale = resolveResponseLocale(dir, premise.text);
445
445
  const got = await elicit(qLocale === 'ko'
446
446
  ? `이 결정에 남겨둔 질문입니다: "${premise.text}". 지금은 어떻게 판단하시나요? 당신의 말로 적어주세요. (그대로 열어둬도 됩니다.)`
447
- : `Your open question on this decision: "${premise.text}". What is your call now, in your own words? (You can also leave it open.)`, { type: 'object', properties: { decision: { type: 'string', maxLength: 400, description: qLocale === 'ko' ? '당신의 판단, 당신의 표현.' : 'Your call, your words.' } }, required: ['decision'] });
447
+ : `Your open question on this decision: "${premise.text}". What is your call now, in your own words? (You can also leave it open.)`,
448
+ // 필수 필드 없음 — 빈 채 Accept는 아래 `if (!decision)`가 정직하게
449
+ // 되묻는다. "아직 못 정했다"도 유효한 답이므로 폼이 막아선 안 된다.
450
+ { type: 'object', properties: { decision: { type: 'string', maxLength: 400, description: qLocale === 'ko' ? '당신의 판단, 당신의 표현. (아직이면 비워두고 Accept)' : 'Your call, your words. (Leave blank and Accept if still undecided.)' } } });
448
451
  decision = typeof got?.['decision'] === 'string' ? got['decision'].trim() : '';
449
452
  }
450
453
  if (!decision) {
@@ -54,7 +54,9 @@ export const settle = {
54
54
  // a bilingual "그렇게 됐다 (held)" mishmash showed to BOTH a Korean and an
55
55
  // English user. Voice follows the language the decision was sealed in.
56
56
  const pickerLocale = resolveResponseLocale(dir, current.predicate ?? null);
57
- const picked = await elicit(pickerLocale === 'ko' ? '현실이 어떻게 답했나요?' : 'What did reality do?', {
57
+ const picked = await elicit(pickerLocale === 'ko'
58
+ ? '현실이 어떻게 답했나요?\n\n결과를 고르고 Accept · 아직 모르겠으면 Decline.'
59
+ : 'What did reality do?\n\nPick the outcome and Accept · Decline if you are not sure yet.', {
58
60
  type: 'object',
59
61
  properties: {
60
62
  outcome: {
@@ -75,7 +77,19 @@ export const settle = {
75
77
  description: pickerLocale === 'ko' ? '무슨 일이 있었는지 당신의 말로 한 줄. (아직 불분명이면 비워도 됩니다.)' : "One line on what actually happened, in your words. (Leave blank if still unclear.)",
76
78
  },
77
79
  },
78
- required: ['outcome'],
80
+ // 필수 필드 없음 (2026-07-27, 창업자 도그푸딩 스크린샷).
81
+ //
82
+ // R34가 봉인/전제 픽커에서 걷어낸 바로 그 패턴이 정산 픽커엔
83
+ // 그대로 남아 있었다. 필수 enum은 호스트에서 접힌 채 뜨고
84
+ // (`→ to expand`), 사용자가 what_happened만 적고 Accept하면 폼
85
+ // 안에서 빨간 "This field is required"로 막힌다 — 우리가 서버에서
86
+ // 없앤 막다름이 클라이언트로 자리만 옮긴 것이다. 하필 정산은
87
+ // 귀환 경로, 이 제품의 두 번째 인상 한복판이다.
88
+ //
89
+ // 비운 채 Accept해도 잃는 게 없다: 아래 `if (!outcome)`가 이미
90
+ // OUTCOME_REQUIRED로 정직하게 되묻고 모델이 대화로 물어본다.
91
+ // 폼 안에서 빨간 글씨로 막느니 한 번 더 묻는 쪽이 낫다.
92
+ // 스파인 무접촉 — 비었다고 결과를 추론하지 않는다.
79
93
  });
80
94
  const v = picked?.['outcome'];
81
95
  if (v === 'held' || v === 'avoided' || v === 'partial' || v === 'still_pending' || v === 'missed')
@@ -271,7 +285,12 @@ async function deferStillPending(args) {
271
285
  // matters should not be forced into a fake future date).
272
286
  let dismissChosen = false;
273
287
  if (!newDate && canElicit()) {
274
- const picked = await elicit(locale === 'ko' ? '아직 답이 안 나왔군요. 언제 다시 볼까요?' : "Not answered yet. When should I look again?", { type: 'object', required: ['when'], properties: { when: {
288
+ const picked = await elicit(locale === 'ko'
289
+ ? '아직 답이 안 나왔군요. 언제 다시 볼까요?\n\n고르고 Accept · 지금 정하기 싫으면 Decline.'
290
+ : "Not answered yet. When should I look again?\n\nPick one and Accept · Decline to leave it for now.",
291
+ // 필수 필드 없음 — 같은 이유. 빈 채 Accept는 Decline과 같은 길로
292
+ // 흐르고(newDate undefined → 아래 정직한 에러), 폼 안에서 막지 않는다.
293
+ { type: 'object', properties: { when: {
275
294
  type: 'string', enum: ['week', 'month', 'quarter', 'dismiss'],
276
295
  enumNames: locale === 'ko'
277
296
  ? ['약 1주 뒤', '약 1달 뒤', '약 3달 뒤', '이제 필요 없음 (접기)']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argus-decision-mcp",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Argus decision-accountability MCP server — clarify a decision, save a falsifiable prediction, and record what reality did. The model never grades you.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",