argus-decision-mcp 1.7.0 → 1.8.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.
- package/dist/lib/ambient-prefs.js +37 -0
- package/dist/lib/spine.js +1 -1
- package/dist/tools/check-in.js +6 -4
- package/dist/tools/premises.js +55 -1
- package/dist/tools/public-tools.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { STANDING_SENSE_REFRESH } from './spine.js';
|
|
5
|
+
export function readAmbientPrefs() {
|
|
6
|
+
const fallback = { sensitivity: 'normal', optOut: false };
|
|
7
|
+
try {
|
|
8
|
+
const home = process.env['ARGUS_HOME'] || path.join(os.homedir(), '.argus');
|
|
9
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(home, 'config.json'), 'utf8'));
|
|
10
|
+
const amb = cfg && typeof cfg['ambient'] === 'object' && cfg['ambient'] !== null
|
|
11
|
+
? cfg['ambient'] : null;
|
|
12
|
+
if (!amb)
|
|
13
|
+
return fallback;
|
|
14
|
+
const s = amb['sensitivity'];
|
|
15
|
+
return {
|
|
16
|
+
sensitivity: s === 'low' || s === 'high' ? s : 'normal',
|
|
17
|
+
optOut: amb['opt_out'] === true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return fallback; // 설정 없음/파손 = 기본 감도 (조용한 기본, 시끄러운 실패 불필요 — 선호 부재는 gap이 아니다)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const LOW_SUFFIX = ' (User sensitivity preference: LOW — offer only on the very clearest consequential commitments; when in any doubt, stay silent.)';
|
|
25
|
+
const HIGH_SUFFIX = ' (User sensitivity preference: HIGH — they asked for more surfacing; the restraint gate still applies, but on a consequential call do not skip the one offer out of excess caution.)';
|
|
26
|
+
const OFF_SUFFIX = ' (User preference: ambient offers are OFF — make no unsolicited predict/capture offers. Recording an outcome the user themselves states is bookkeeping and stays on.)';
|
|
27
|
+
/** 감도 선호를 반영한 배경감각 줄 — 모든 주입 지점은 이걸 쓴다(직접 상수 금지). */
|
|
28
|
+
export function tunedStandingSense() {
|
|
29
|
+
const p = readAmbientPrefs();
|
|
30
|
+
if (p.optOut)
|
|
31
|
+
return STANDING_SENSE_REFRESH + OFF_SUFFIX;
|
|
32
|
+
if (p.sensitivity === 'low')
|
|
33
|
+
return STANDING_SENSE_REFRESH + LOW_SUFFIX;
|
|
34
|
+
if (p.sensitivity === 'high')
|
|
35
|
+
return STANDING_SENSE_REFRESH + HIGH_SUFFIX;
|
|
36
|
+
return STANDING_SENSE_REFRESH;
|
|
37
|
+
}
|
package/dist/lib/spine.js
CHANGED
|
@@ -80,7 +80,7 @@ export const SERVER_INSTRUCTIONS = [
|
|
|
80
80
|
* 뿐이므로, Argus 툴이 불릴 때마다 이 한 줄이 배경감각을 되살린다). 지침이지
|
|
81
81
|
* 평결이 아니며, SERVER_INSTRUCTIONS의 3감각과 같은 내용의 압축이다.
|
|
82
82
|
*/
|
|
83
|
-
export const STANDING_SENSE_REFRESH = 'Background sense from here on: the open predictions above are unsettled. If the user\'s ongoing words reveal what reality did to one — even in passing, even by pronoun ("그거 결국 잘 됐어") — record it with argus_resolve in their words THEN; never infer an outcome they did not state. Keep noticing new checkable claims (argus_predict, offer once on a consequential one) and the load-bearing assumption a decision rests on — often unstated (argus_capture, at most one, as your ai_surfaced draft). But stay silent when the turn only asks you to do a task, poses a logistics or recommendation question, is small talk, or concerns a call the user already closed — and once they have moved on to another topic. A blank is honest; when unsure, do nothing.';
|
|
83
|
+
export const STANDING_SENSE_REFRESH = 'Background sense from here on: the open predictions above are unsettled. If the user\'s ongoing words reveal what reality did to one — even in passing, even by pronoun ("그거 결국 잘 됐어") — record it with argus_resolve in their words THEN; never infer an outcome they did not state. Keep noticing new checkable claims (argus_predict, offer once on a consequential one) and the load-bearing assumption a decision rests on — often unstated (argus_capture, at most one, as your ai_surfaced draft). But stay silent when the turn only asks you to do a task, poses a logistics or recommendation question, is small talk, or concerns a call the user already closed — and once they have moved on to another topic. ("Only a task" holds only while no consequential call rides under it: a plan handed to you as a to-do — "here is the plan, start with X" — is still a plan; do the task AND offer its single load-bearing premise once.) A blank is honest; when unsure, do nothing.';
|
|
84
84
|
/** The schema version stamped on every persisted record (addendum N1). */
|
|
85
85
|
export const SCHEMA_VERSION = 1;
|
|
86
86
|
/**
|
package/dist/tools/check-in.js
CHANGED
|
@@ -5,8 +5,9 @@ import { duePremises, groupDuePremises, dueOpenQuestions } from '../lib/premises
|
|
|
5
5
|
import { readReceipt, SKIPPED } from '../lib/receipt.js';
|
|
6
6
|
import { SURFACES, resolveResponseLocale, surfaceLocale } from '../lib/surfaces.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import {
|
|
8
|
+
import { tunedStandingSense } from '../lib/ambient-prefs.js';
|
|
9
9
|
import { envelope } from '../lib/envelope.js';
|
|
10
|
+
import { canElicit } from '../lib/elicit.js';
|
|
10
11
|
import { ENVELOPE_OUTPUT_SCHEMA, zArgusDir, zDate } from './tool-types.js';
|
|
11
12
|
import { handleToolException } from './errors.js';
|
|
12
13
|
import { briefDivergence, readV2Brief } from '../v2/mirror.js';
|
|
@@ -271,14 +272,14 @@ export const checkIn = {
|
|
|
271
272
|
ok: true, tool: 'argus_check_in',
|
|
272
273
|
surface: S.first_run,
|
|
273
274
|
next_actions: ['argus_capture'],
|
|
274
|
-
data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, first_run: true, today },
|
|
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' },
|
|
275
276
|
});
|
|
276
277
|
}
|
|
277
278
|
return envelope({
|
|
278
279
|
ok: true, tool: 'argus_check_in',
|
|
279
280
|
surface: mirrorLine + S.nothing_due + accountHint + upcomingLine + fleetLine + integrityLine,
|
|
280
281
|
next_actions: ['stop'],
|
|
281
|
-
data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, ...(openWatch.length ? { open_predictions: openWatch, standing_sense:
|
|
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)) } : {}) },
|
|
282
283
|
});
|
|
283
284
|
}
|
|
284
285
|
const parts = [];
|
|
@@ -312,6 +313,7 @@ export const checkIn = {
|
|
|
312
313
|
surface: mirrorLine + parts.join(' ') + upcomingLine + fleetLine + integrityLine,
|
|
313
314
|
next_actions: next,
|
|
314
315
|
data: {
|
|
316
|
+
picker: canElicit() ? 'one_tap' : 'text_fallback',
|
|
315
317
|
due: dueEnriched, due_count: dueAll.length,
|
|
316
318
|
...(dueTruncated > 0 ? { due_truncated: `${dueAll.length} due, showing ${DUE_TOP} oldest` } : {}),
|
|
317
319
|
due_premises: duePrem, due_premise_count: premiseGroups.length,
|
|
@@ -321,7 +323,7 @@ export const checkIn = {
|
|
|
321
323
|
...(upDays > 0 ? { upcoming } : {}),
|
|
322
324
|
...(a['fleet'] === true ? { fleet: fleetRows } : {}),
|
|
323
325
|
...watchData,
|
|
324
|
-
...(openWatch.length ? { open_predictions: openWatch, standing_sense:
|
|
326
|
+
...(openWatch.length ? { open_predictions: openWatch, standing_sense: tunedStandingSense() } : {}),
|
|
325
327
|
today, integrity: ledger.integrity,
|
|
326
328
|
// v2 병기/진단은 ARGUS_V2_DEBUG=1 뒤로. 공개 payload에 싣던 v2_brief가
|
|
327
329
|
// 머신-전역 durable-home 저장소를 읽어 다른 프로젝트의 결정 원문을
|
package/dist/tools/premises.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveContract } from '../lib/resolve-contract.js';
|
|
|
6
6
|
import { guardTransition } from '../lib/state-machine.js';
|
|
7
7
|
import { appendLedger, withLedgerLock } from '../lib/ledger-append.js';
|
|
8
8
|
import { premiseId, resolvePremiseRef, normalizePremiseText, MAX_ACTIVE_PREMISES, MAX_LOAD_BEARING, } from '../lib/premises.js';
|
|
9
|
-
import { elicit } from '../lib/elicit.js';
|
|
9
|
+
import { elicit, canElicit } from '../lib/elicit.js';
|
|
10
10
|
import { resolveResponseLocale } from '../lib/surfaces.js';
|
|
11
11
|
import { envelope, toolError } from '../lib/envelope.js';
|
|
12
12
|
import { ENVELOPE_OUTPUT_SCHEMA, zArgusDir, zId, zDate } from './tool-types.js';
|
|
@@ -188,6 +188,59 @@ async function opAdd(dir, id, today, now, state, existing, a) {
|
|
|
188
188
|
return toolError({ ok: false, tool: 'argus_premises', error_code: 'PROVENANCE_REQUIRED', message: `source="ai_surfaced" requires ai_original (the model's original wording): "${p.text.slice(0, 60)}"`, recovery: 'Set ai_original to the exact model-drafted sentence, or source="user_stated" if the user wrote it.' });
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
+
// One-tap confirm for a DRAFTED premise — seal's picker, mirrored (the ask
|
|
192
|
+
// must be a structural picker, not prose the model may skip). Fires only in
|
|
193
|
+
// the sense's canonical case: exactly ONE ai_surfaced draft in the call
|
|
194
|
+
// (multi-premise structured flows confirm in their own conversation). Keep
|
|
195
|
+
// records it with provenance ai_surfaced UNCHANGED — a tap approves the
|
|
196
|
+
// recording, it does not transfer authorship (predictions differ: a bet must
|
|
197
|
+
// become the user's; a premise is a mirror observation whose honest tag IS
|
|
198
|
+
// the invariant). Reword typed in the form → the user's words, user_stated,
|
|
199
|
+
// with the draft preserved as ai_original. Skip / declined / no elicitation
|
|
200
|
+
// → the friction escape stays: nothing forced, no dead end.
|
|
201
|
+
{
|
|
202
|
+
const aiDrafts = inputs.filter((p) => normalizePremiseSource(p.source) === 'ai_surfaced');
|
|
203
|
+
if (aiDrafts.length === 1 && canElicit()) {
|
|
204
|
+
const draft = aiDrafts[0];
|
|
205
|
+
const dLocale = resolveResponseLocale(dir, draft.text);
|
|
206
|
+
const picked = await elicit(dLocale === 'ko'
|
|
207
|
+
? `이 결정이 딛고 선 전제로 기록할까요?\n"${draft.text}"`
|
|
208
|
+
: `Record this as a premise the decision rests on?\n"${draft.text}"`, { type: 'object', required: ['choice'], properties: {
|
|
209
|
+
choice: {
|
|
210
|
+
type: 'string', enum: ['keep', 'reword', 'skip'],
|
|
211
|
+
enumNames: dLocale === 'ko' ? ['그대로 기록', '직접 고쳐 쓰기', '건너뛰기'] : ['Keep it', 'Let me reword', 'Skip'],
|
|
212
|
+
description: dLocale === 'ko' ? '이 전제를 기록할지 고르세요.' : 'Whether to record this premise.',
|
|
213
|
+
},
|
|
214
|
+
your_wording: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
description: dLocale === 'ko' ? '"직접 고쳐 쓰기"를 골랐다면 원하는 문장을 여기에 적어 주세요. 그 말 그대로 저장됩니다.' : 'If you chose "Let me reword", type the premise here. It is saved exactly as written.',
|
|
217
|
+
},
|
|
218
|
+
} });
|
|
219
|
+
const choice = picked?.['choice'];
|
|
220
|
+
if (choice === 'reword') {
|
|
221
|
+
const wording = typeof picked?.['your_wording'] === 'string' ? picked['your_wording'].trim() : '';
|
|
222
|
+
if (wording.length >= 4 && wording.length <= 400) {
|
|
223
|
+
draft.ai_original = draft.ai_original ?? draft.text;
|
|
224
|
+
draft.text = wording;
|
|
225
|
+
draft.source = 'user_stated';
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// Reword chosen but no usable wording (or an enum-only form) — the
|
|
229
|
+
// two-step fallback: ask in chat, the model re-calls with their words.
|
|
230
|
+
return envelope({ ok: true, tool: 'argus_premises', surface: dLocale === 'ko' ? '그럼 그 전제를 원하는 문장으로 알려주세요. 그 말 그대로 기록하겠습니다.' : 'Then tell me the premise in your own words and I will record exactly that.', next_actions: ['argus_capture'], data: { recorded: false, choice: 'reword' } });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (choice !== 'keep') {
|
|
234
|
+
// skip, or a declined/cancelled picker — drop ONLY the draft; the
|
|
235
|
+
// user's own premises in the same call still record below.
|
|
236
|
+
inputs.splice(inputs.indexOf(draft), 1);
|
|
237
|
+
if (inputs.length === 0) {
|
|
238
|
+
return envelope({ ok: true, tool: 'argus_premises', surface: dLocale === 'ko' ? '기록하지 않았습니다.' : 'Not recorded.', next_actions: ['stop'], data: { recorded: false, choice: choice ?? 'declined' } });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// keep → recorded as drafted below, provenance ai_surfaced intact.
|
|
242
|
+
}
|
|
243
|
+
}
|
|
191
244
|
// Dedup against the ledger by stable id. Three cases, kept distinct so a
|
|
192
245
|
// re-add is never silently swallowed behind a misleading "already recorded":
|
|
193
246
|
// - collides with an ACTIVE premise (same text) → true idempotent dup (skip)
|
|
@@ -273,6 +326,7 @@ async function opAdd(dir, id, today, now, state, existing, a) {
|
|
|
273
326
|
const echo = events.map((e) => ({
|
|
274
327
|
ref: `P${e.ordinal}`, premise_id: e.premise_id, kind: e.kind, text: e.text,
|
|
275
328
|
external: e.external, load_bearing: e.load_bearing, source: e.source,
|
|
329
|
+
...(e['ai_original'] ? { ai_original: e['ai_original'] } : {}),
|
|
276
330
|
monitored: e.kind === 'premise' && e.external === true && e.load_bearing === true,
|
|
277
331
|
}));
|
|
278
332
|
const monitoredCount = echo.filter((p) => p.monitored).length;
|
|
@@ -5,7 +5,7 @@ import { resolveToolArgusDir } from '../lib/argus-dir.js';
|
|
|
5
5
|
import { sanitizeOutput } from '../lib/untrusted.js';
|
|
6
6
|
import { replayLedger } from '../lib/ledger-replay.js';
|
|
7
7
|
import { resolveToday } from '../lib/resolve-today.js';
|
|
8
|
-
import {
|
|
8
|
+
import { tunedStandingSense } from '../lib/ambient-prefs.js';
|
|
9
9
|
import { configPath } from '../lib/layout.js';
|
|
10
10
|
import { ENVELOPE_OUTPUT_SCHEMA, zArgusDir, zDate, zId } from './tool-types.js';
|
|
11
11
|
import { openDecision } from './open-decision.js';
|
|
@@ -299,7 +299,7 @@ function attachOpenPredictions(result, args) {
|
|
|
299
299
|
// 원장 predicate(사용자 저작 텍스트)를 직접 세탁해야 한다 — 안 하면 ANSI/bidi/
|
|
300
300
|
// zero-width 벡터가 세탁 안 된 채 모델에 직행하는 새 경로가 된다.
|
|
301
301
|
data['open_predictions'] = sanitizeOutput(open);
|
|
302
|
-
data['standing_sense'] =
|
|
302
|
+
data['standing_sense'] = tunedStandingSense();
|
|
303
303
|
result.content = [{ type: 'text', text: JSON.stringify(sc, null, 2) }];
|
|
304
304
|
}
|
|
305
305
|
catch { /* 라이더 실패는 침묵 — 본 결과를 해치지 않는다 */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argus-decision-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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",
|