argus-decision-mcp 1.4.2 → 1.4.6
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/dist/lib/calendar.js +0 -0
- package/dist/lib/due-note.js +6 -1
- package/dist/lib/elicit.js +9 -1
- package/dist/lib/ledger-append.js +91 -19
- package/dist/lib/ledger-replay.js +32 -10
- package/dist/lib/localize-result.js +64 -14
- package/dist/lib/numeric-drift.js +2 -2
- package/dist/lib/receipt.js +17 -1
- package/dist/lib/render-receipt.js +36 -11
- package/dist/lib/resolve-today.js +20 -0
- package/dist/lib/safe-path.js +16 -0
- package/dist/lib/surfaces.js +18 -14
- package/dist/lib/tool-presentation.js +1 -1
- package/dist/lib/untrusted.js +3 -3
- package/dist/lib/validate-seal.js +6 -3
- package/dist/resources.js +6 -1
- package/dist/server.js +15 -10
- package/dist/tools/amend-dismiss.js +21 -0
- package/dist/tools/check-in.js +24 -1
- package/dist/tools/errors.js +4 -4
- package/dist/tools/init-config.js +14 -0
- package/dist/tools/premises.js +22 -4
- package/dist/tools/public-tools.js +20 -7
- package/dist/tools/recall.js +14 -6
- package/dist/tools/recheck.js +41 -6
- package/dist/tools/seal.js +11 -8
- package/dist/tools/settle.js +18 -10
- package/dist/tools/tool-types.js +31 -5
- package/package.json +1 -1
package/dist/tools/seal.js
CHANGED
|
@@ -65,12 +65,15 @@ export const seal = {
|
|
|
65
65
|
}
|
|
66
66
|
let predicate = String(a['predicate']);
|
|
67
67
|
const checkBy = String(a['check_by']);
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
68
|
+
// The DATE part of `now` must equal the tz-aware logical `today`. Plain
|
|
69
|
+
// new Date().toISOString() is always UTC, so a Korea (UTC+9) user sealing
|
|
70
|
+
// at 08:00 KST (= 23:00Z the day before) got a receipt dated YESTERDAY —
|
|
71
|
+
// the tool's own `today` disagreeing with the date it printed. recheck.ts
|
|
72
|
+
// already fixed this same class for premise cadences. Keep the real UTC
|
|
73
|
+
// time-of-day for intra-day ordering, but stamp the logical date.
|
|
74
|
+
const now = a['today_override']
|
|
75
|
+
? `${today}T12:00:00.000Z`
|
|
76
|
+
: `${today}T${new Date().toISOString().slice(11)}`;
|
|
74
77
|
// Response voice follows the predicate (M4): config > text > env.
|
|
75
78
|
let locale = resolveResponseLocale(dir, predicate);
|
|
76
79
|
let T = SURFACES[locale].tools.seal;
|
|
@@ -196,7 +199,7 @@ export const seal = {
|
|
|
196
199
|
await atomicWriteJson(bearingPath(dir, id), {
|
|
197
200
|
v: SCHEMA_VERSION, id, contract_seed: { predicate, check_by: checkBy }, predicate_owner: a['predicate_owner'],
|
|
198
201
|
});
|
|
199
|
-
const calendarPathW = await writeReturnCalendarEvent(dir, { id, predicate, check_by: checkBy, created_at: now });
|
|
202
|
+
const calendarPathW = await writeReturnCalendarEvent(dir, { id, predicate, check_by: checkBy, created_at: now, locale });
|
|
200
203
|
// 미러 힌트: 원장 이벤트에 없는 elicit 목격 여부 + 영수증 필드만 —
|
|
201
204
|
// 미러 자체는 appendLedger의 단일 관문이 수행한다 (mirror.ts).
|
|
202
205
|
const appended = await appendLedger(dir, events, now, { seal: {
|
|
@@ -261,7 +264,7 @@ export const seal = {
|
|
|
261
264
|
// dumping the absolute path — and the English label "Calendar file:" — into
|
|
262
265
|
// a one-line surface was noise, and broke the Korean voice (copy-audit /
|
|
263
266
|
// loop find). Mention it briefly, localized; keep the path in data.
|
|
264
|
-
const calNote = locale === 'ko' ? ' 달력 리마인더(.ics)도
|
|
267
|
+
const calNote = locale === 'ko' ? ' 달력 리마인더(.ics)도 저장했습니다.' : ' Saved a calendar reminder (.ics).';
|
|
265
268
|
return envelope({
|
|
266
269
|
ok: true, tool: 'argus_seal',
|
|
267
270
|
surface: `${T.sealed(predicate, checkBy)}${calNote}${nudge}${syncLine}`,
|
package/dist/tools/settle.js
CHANGED
|
@@ -21,8 +21,8 @@ import { handleToolException } from './errors.js';
|
|
|
21
21
|
const inputSchema = z.strictObject({
|
|
22
22
|
argus_dir: zArgusDir,
|
|
23
23
|
id: zId,
|
|
24
|
-
outcome: z.enum(['held', 'avoided', 'partial', 'still_pending', 'missed']).describe("What reality did to the prediction
|
|
25
|
-
outcome_source: z.literal('user_stated').describe('
|
|
24
|
+
outcome: z.enum(['held', 'avoided', 'partial', 'still_pending', 'missed']).describe("What reality did to the prediction — record the user's words, never infer. Definitions (held and avoided are counted separately, so pick the right one): held = the predicted thing actually happened; avoided = the predicted RISK did not occur (the bad thing you predicted was dodged); partial = it half-happened / mixed; missed = the saved prediction was simply wrong; still_pending = reality has not answered yet, so pass a future defer_to and it comes back. If omitted, Argus asks the user directly on hosts that support elicitation.").optional(),
|
|
25
|
+
outcome_source: z.literal('user_stated').default('user_stated').describe('Always "user_stated" — an AI-inferred outcome cannot be expressed. Defaulted, so you may omit it.'),
|
|
26
26
|
what_happened: z.string().min(1).max(600).optional().describe("What reality did, in the user's words. Required when recording held/avoided/partial/missed. Omit for still_pending and pass defer_to instead."),
|
|
27
27
|
broken_premise_ref: z.string().max(64).optional().describe('Optional, USER-attributed: which tracked premise (ordinal like "P1"), if any, broke and drove the outcome. Never inferred by the model — ask, or omit.'),
|
|
28
28
|
defer_to: zDate.optional().describe("Only with outcome='still_pending': the new check-by (YYYY-MM-DD, a real future date) — when to look again, taken from the horizon the user names (\"the data lands next Friday\"). The decision stays alive and comes due again then. Omit only if the user has not said when; on elicitation hosts Argus will ask."),
|
|
@@ -50,14 +50,20 @@ export const settle = {
|
|
|
50
50
|
// without elicitation.
|
|
51
51
|
let outcome = a['outcome'];
|
|
52
52
|
if (!outcome && canElicit()) {
|
|
53
|
-
|
|
53
|
+
// Localize the picker like every other elicitation (ambient-elicit does):
|
|
54
|
+
// a bilingual "그렇게 됐다 (held)" mishmash showed to BOTH a Korean and an
|
|
55
|
+
// English user. Voice follows the language the decision was sealed in.
|
|
56
|
+
const pickerLocale = resolveResponseLocale(dir, current.predicate ?? null);
|
|
57
|
+
const picked = await elicit(pickerLocale === 'ko' ? '현실이 어떻게 답했나요?' : 'What did reality do?', {
|
|
54
58
|
type: 'object',
|
|
55
59
|
properties: {
|
|
56
60
|
outcome: {
|
|
57
61
|
type: 'string',
|
|
58
62
|
enum: ['held', 'avoided', 'partial', 'still_pending', 'missed'],
|
|
59
|
-
enumNames:
|
|
60
|
-
|
|
63
|
+
enumNames: pickerLocale === 'ko'
|
|
64
|
+
? ['그렇게 됐다', '피했다', '부분적으로', '아직 불분명', '빗나갔다 (내 예측이 틀렸다)']
|
|
65
|
+
: ['It held', 'Avoided', 'Partially', 'Still unclear', 'Missed — my read was wrong'],
|
|
66
|
+
description: pickerLocale === 'ko' ? '봉인한 예측에 현실이 어떻게 답했는지 고르세요.' : 'What reality did to your sealed prediction.',
|
|
61
67
|
},
|
|
62
68
|
},
|
|
63
69
|
required: ['outcome'],
|
|
@@ -77,11 +83,13 @@ export const settle = {
|
|
|
77
83
|
// Response voice follows what-happened (M4): config > text > env.
|
|
78
84
|
const locale = resolveResponseLocale(dir, a['what_happened']);
|
|
79
85
|
const T = SURFACES[locale].tools.settle;
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
const now = a['today_override']
|
|
86
|
+
// The DATE part of `now` must equal the tz-aware logical `today`, else a
|
|
87
|
+
// Korea (UTC+9) user settling at 08:00 KST gets a receipt dated yesterday
|
|
88
|
+
// (raw UTC). Keep the real UTC time-of-day for ordering; stamp the logical
|
|
89
|
+
// date. (Same fix as seal.ts; recheck.ts fixed it for premise cadences.)
|
|
90
|
+
const now = a['today_override']
|
|
91
|
+
? `${today}T12:00:00.000Z`
|
|
92
|
+
: `${today}T${new Date().toISOString().slice(11)}`;
|
|
85
93
|
// still_pending = reality has NOT answered yet. This is NOT a settlement —
|
|
86
94
|
// filing it as `settled` (terminal) silently closed the loop and dropped
|
|
87
95
|
// the decision off check_in forever, while the surface lied "what actually
|
package/dist/tools/tool-types.js
CHANGED
|
@@ -7,7 +7,11 @@ export const zArgusDir = z
|
|
|
7
7
|
.string()
|
|
8
8
|
.describe('Absolute path to the .argus directory (no ".."). Omit to use the ARGUS_DIR env var from your MCP config.')
|
|
9
9
|
.optional();
|
|
10
|
-
|
|
10
|
+
// The single id type: 1–128 chars, [A-Za-z0-9._-]. The bound lives here so
|
|
11
|
+
// every tool (predict/amend/dismiss/recheck) advertises the SAME limit the
|
|
12
|
+
// runtime path guard enforces — previously bare `zId` promised an unbounded id
|
|
13
|
+
// that then threw a PathSafetyError deep in the write path.
|
|
14
|
+
export const zId = z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+$/, 'id may only contain A-Z a-z 0-9 . _ -');
|
|
11
15
|
export const zDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'must be YYYY-MM-DD');
|
|
12
16
|
const KO_FIELD_DESCRIPTIONS = {
|
|
13
17
|
argus_dir: '프로젝트의 .argus 절대 경로입니다. 생략하면 MCP 설정의 ARGUS_DIR을 사용합니다.',
|
|
@@ -17,7 +21,7 @@ const KO_FIELD_DESCRIPTIONS = {
|
|
|
17
21
|
reversibility: '결정을 되돌릴 수 있는 정도입니다.',
|
|
18
22
|
status_quo: '아무것도 하지 않을 때 일어나는 일입니다.',
|
|
19
23
|
already_decided: '이미 결정을 내렸는지 여부입니다.',
|
|
20
|
-
crux_question: '결정을
|
|
24
|
+
crux_question: '결정을 좌우하는 중립적인 핵심 질문 하나입니다.',
|
|
21
25
|
load_bearing_assumption: '결정이 가장 크게 기대는 전제 하나입니다.',
|
|
22
26
|
related_to: '사용자가 비슷하다고 본 과거 결정 id입니다.',
|
|
23
27
|
today_override: '테스트 또는 명시적 기준일에만 사용하는 오늘 날짜입니다.',
|
|
@@ -105,7 +109,7 @@ const EN_FIELD_DESCRIPTIONS = {
|
|
|
105
109
|
reversibility: 'How difficult the decision is to reverse.',
|
|
106
110
|
status_quo: 'What happens if nothing changes.',
|
|
107
111
|
already_decided: 'Whether the user has already made the decision.',
|
|
108
|
-
crux_question: '
|
|
112
|
+
crux_question: 'The one neutral, load-bearing question the decision turns on.',
|
|
109
113
|
load_bearing_assumption: 'The single assumption the decision depends on most.',
|
|
110
114
|
related_to: 'Past decision ids the user considers related.',
|
|
111
115
|
action: 'The operation to perform.',
|
|
@@ -158,7 +162,14 @@ const EN_FIELD_DESCRIPTIONS = {
|
|
|
158
162
|
};
|
|
159
163
|
/** JSON Schema for tools/list, generated from the Zod source (drop $schema noise). */
|
|
160
164
|
export function toolJsonSchema(schema) {
|
|
161
|
-
|
|
165
|
+
// io:'input' — this schema describes the tool's ARGUMENTS. Zod v4 defaults
|
|
166
|
+
// z.toJSONSchema to io:'output', which marks every `.default()` field as
|
|
167
|
+
// REQUIRED (while still emitting its default) — so a strict host / the MCP
|
|
168
|
+
// Inspector would reject `argus_check_in {}` (the mandated session-start call),
|
|
169
|
+
// `argus_patterns {}`, and a premise without kind/external/load_bearing, even
|
|
170
|
+
// though the runtime validator fills those defaults. Input mode advertises them
|
|
171
|
+
// as optional; additionalProperties:false (strictObject) is preserved.
|
|
172
|
+
const json = z.toJSONSchema(schema, { io: 'input' });
|
|
162
173
|
delete json['$schema'];
|
|
163
174
|
const visit = (node) => {
|
|
164
175
|
if (!node || typeof node !== 'object')
|
|
@@ -174,7 +185,16 @@ export function toolJsonSchema(schema) {
|
|
|
174
185
|
if (ko) {
|
|
175
186
|
const existing = typeof field['description'] === 'string' ? field['description'].trim() : '';
|
|
176
187
|
const en = EN_FIELD_DESCRIPTIONS[key] ?? existing;
|
|
177
|
-
|
|
188
|
+
const base = en ? `${ko}\n\n${en}` : ko;
|
|
189
|
+
// A richer Zod .describe() (e.g. the argus_patterns `view` enum-value
|
|
190
|
+
// glossary) must not be dropped by the short bilingual map — append it
|
|
191
|
+
// when it carries more than the base already says. Never append a
|
|
192
|
+
// describe that references an internal tool name (argus_*): the
|
|
193
|
+
// bilingual map masks those on purpose, and re-surfacing one leaks an
|
|
194
|
+
// internal name into tools/list (public-surface-names guard).
|
|
195
|
+
field['description'] = existing.length > base.length && !base.includes(existing) && !/argus_/.test(existing)
|
|
196
|
+
? `${base}\n\n${existing}`
|
|
197
|
+
: base;
|
|
178
198
|
}
|
|
179
199
|
visit(field);
|
|
180
200
|
}
|
|
@@ -201,6 +221,12 @@ export const ENVELOPE_OUTPUT_SCHEMA = {
|
|
|
201
221
|
over_fire_gate: { type: 'object' },
|
|
202
222
|
error_code: { type: 'string' },
|
|
203
223
|
message: { type: 'string' },
|
|
224
|
+
// error results also carry these — declared so a host that validates
|
|
225
|
+
// structuredContent against outputSchema on ERROR results (the SDK does, even
|
|
226
|
+
// when isError) would still pass if the schema were ever hardened to
|
|
227
|
+
// additionalProperties:false.
|
|
228
|
+
recovery: { type: 'string' },
|
|
229
|
+
invalid_fields: { type: 'array', items: { type: 'object' } },
|
|
204
230
|
},
|
|
205
231
|
required: ['ok', 'tool'],
|
|
206
232
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argus-decision-mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
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",
|