@phren/cli 0.0.10 → 0.0.11
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 +2 -8
- package/mcp/dist/cli-actions.js +5 -5
- package/mcp/dist/cli-config.js +334 -127
- package/mcp/dist/cli-govern.js +35 -63
- package/mcp/dist/cli-graph.js +3 -2
- package/mcp/dist/cli-hooks-globs.js +2 -1
- package/mcp/dist/cli-hooks-output.js +3 -3
- package/mcp/dist/cli-hooks.js +39 -32
- package/mcp/dist/cli-namespaces.js +15 -5
- package/mcp/dist/cli-search.js +2 -2
- package/mcp/dist/content-archive.js +2 -2
- package/mcp/dist/content-dedup.js +9 -9
- package/mcp/dist/embedding.js +7 -7
- package/mcp/dist/entrypoint.js +129 -102
- package/mcp/dist/governance-locks.js +6 -5
- package/mcp/dist/governance-policy.js +155 -2
- package/mcp/dist/governance-scores.js +3 -3
- package/mcp/dist/hooks.js +39 -18
- package/mcp/dist/index.js +4 -4
- package/mcp/dist/init-config.js +3 -24
- package/mcp/dist/init-setup.js +5 -5
- package/mcp/dist/init.js +170 -23
- package/mcp/dist/link-checksums.js +3 -2
- package/mcp/dist/link-context.js +1 -1
- package/mcp/dist/link-doctor.js +3 -3
- package/mcp/dist/link-skills.js +98 -12
- package/mcp/dist/link.js +17 -27
- package/mcp/dist/machine-identity.js +1 -9
- package/mcp/dist/mcp-config.js +247 -42
- package/mcp/dist/mcp-data.js +9 -9
- package/mcp/dist/mcp-extract-facts.js +1 -1
- package/mcp/dist/mcp-extract.js +2 -2
- package/mcp/dist/mcp-finding.js +6 -6
- package/mcp/dist/mcp-graph.js +11 -11
- package/mcp/dist/mcp-ops.js +18 -18
- package/mcp/dist/mcp-search.js +8 -8
- package/mcp/dist/memory-ui-page.js +23 -0
- package/mcp/dist/memory-ui-scripts.js +210 -27
- package/mcp/dist/memory-ui-server.js +115 -3
- package/mcp/dist/phren-paths.js +7 -7
- package/mcp/dist/profile-store.js +2 -2
- package/mcp/dist/project-config.js +63 -16
- package/mcp/dist/session-utils.js +3 -2
- package/mcp/dist/shared-fragment-graph.js +22 -21
- package/mcp/dist/shared-index.js +144 -105
- package/mcp/dist/shared-retrieval.js +19 -13
- package/mcp/dist/shared-search-fallback.js +13 -13
- package/mcp/dist/shared-sqljs.js +3 -2
- package/mcp/dist/shared.js +3 -3
- package/mcp/dist/shell-input.js +1 -1
- package/mcp/dist/shell-state-store.js +1 -1
- package/mcp/dist/shell-view.js +3 -2
- package/mcp/dist/shell.js +1 -1
- package/mcp/dist/skill-files.js +4 -10
- package/mcp/dist/skill-registry.js +3 -0
- package/mcp/dist/status.js +41 -13
- package/mcp/dist/task-hygiene.js +1 -1
- package/mcp/dist/telemetry.js +5 -4
- package/mcp/dist/update.js +1 -1
- package/mcp/dist/utils.js +3 -3
- package/package.json +2 -2
- package/starter/global/skills/audit.md +106 -0
- package/mcp/dist/shared-paths.js +0 -1
package/mcp/dist/mcp-finding.js
CHANGED
|
@@ -236,7 +236,7 @@ export function register(server, ctx) {
|
|
|
236
236
|
extraAnnotationsByFinding.push(conflicts.checked && conflicts.annotations.length > 0 ? conflicts.annotations : []);
|
|
237
237
|
}
|
|
238
238
|
catch (err) {
|
|
239
|
-
if ((process.env.PHREN_DEBUG
|
|
239
|
+
if ((process.env.PHREN_DEBUG))
|
|
240
240
|
process.stderr.write(`[phren] add_findings semanticConflict: ${errorMessage(err)}\n`);
|
|
241
241
|
extraAnnotationsByFinding.push([]);
|
|
242
242
|
}
|
|
@@ -499,7 +499,7 @@ export function register(server, ctx) {
|
|
|
499
499
|
hasRemote = remotes.length > 0;
|
|
500
500
|
}
|
|
501
501
|
catch (err) {
|
|
502
|
-
if ((process.env.PHREN_DEBUG
|
|
502
|
+
if ((process.env.PHREN_DEBUG))
|
|
503
503
|
process.stderr.write(`[phren] push_changes remoteCheck: ${errorMessage(err)}\n`);
|
|
504
504
|
}
|
|
505
505
|
if (!hasRemote) {
|
|
@@ -523,7 +523,7 @@ export function register(server, ctx) {
|
|
|
523
523
|
runGit(["pull", "--rebase", "--quiet"], { timeout: 15000 });
|
|
524
524
|
}
|
|
525
525
|
catch (pullErr) {
|
|
526
|
-
if ((process.env.PHREN_DEBUG
|
|
526
|
+
if ((process.env.PHREN_DEBUG))
|
|
527
527
|
process.stderr.write(`[phren] push_changes pullRebase: ${pullErr instanceof Error ? pullErr.message : String(pullErr)}\n`);
|
|
528
528
|
const resolved = autoMergeConflicts(phrenPath);
|
|
529
529
|
if (resolved) {
|
|
@@ -534,13 +534,13 @@ export function register(server, ctx) {
|
|
|
534
534
|
});
|
|
535
535
|
}
|
|
536
536
|
catch (continueErr) {
|
|
537
|
-
if ((process.env.PHREN_DEBUG
|
|
537
|
+
if ((process.env.PHREN_DEBUG))
|
|
538
538
|
process.stderr.write(`[phren] push_changes rebaseContinue: ${continueErr instanceof Error ? continueErr.message : String(continueErr)}\n`);
|
|
539
539
|
try {
|
|
540
540
|
runGit(["rebase", "--abort"]);
|
|
541
541
|
}
|
|
542
542
|
catch (abortErr) {
|
|
543
|
-
if ((process.env.PHREN_DEBUG
|
|
543
|
+
if ((process.env.PHREN_DEBUG))
|
|
544
544
|
process.stderr.write(`[phren] push_changes rebaseAbort: ${abortErr instanceof Error ? abortErr.message : String(abortErr)}\n`);
|
|
545
545
|
}
|
|
546
546
|
break;
|
|
@@ -551,7 +551,7 @@ export function register(server, ctx) {
|
|
|
551
551
|
runGit(["rebase", "--abort"]);
|
|
552
552
|
}
|
|
553
553
|
catch (abortErr) {
|
|
554
|
-
if ((process.env.PHREN_DEBUG
|
|
554
|
+
if ((process.env.PHREN_DEBUG))
|
|
555
555
|
process.stderr.write(`[phren] push_changes rebaseAbort2: ${abortErr instanceof Error ? abortErr.message : String(abortErr)}\n`);
|
|
556
556
|
}
|
|
557
557
|
break;
|
package/mcp/dist/mcp-graph.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mcpResponse } from "./mcp-types.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
|
-
import { isValidProjectName } from "./utils.js";
|
|
5
|
+
import { isValidProjectName, errorMessage } from "./utils.js";
|
|
6
6
|
import { queryDocBySourceKey, queryRows, queryFragmentLinks, queryCrossProjectFragments, ensureGlobalEntitiesTable, logFragmentMiss } from "./shared-index.js";
|
|
7
7
|
import { runtimeFile } from "./shared.js";
|
|
8
8
|
import { withFileLock } from "./shared-governance.js";
|
|
@@ -209,8 +209,8 @@ export function register(server, ctx) {
|
|
|
209
209
|
db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [fragmentName, resolvedFragmentType, new Date().toISOString().slice(0, 10)]);
|
|
210
210
|
}
|
|
211
211
|
catch (err) {
|
|
212
|
-
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG
|
|
213
|
-
process.stderr.write(`[phren] link_findings fragmentInsert: ${
|
|
212
|
+
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
|
|
213
|
+
process.stderr.write(`[phren] link_findings fragmentInsert: ${errorMessage(err)}\n`);
|
|
214
214
|
}
|
|
215
215
|
const fragmentResult = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [fragmentName, resolvedFragmentType]);
|
|
216
216
|
if (!fragmentResult?.length || !fragmentResult[0]?.values?.length) {
|
|
@@ -232,8 +232,8 @@ export function register(server, ctx) {
|
|
|
232
232
|
db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [sourceDoc, "document", new Date().toISOString().slice(0, 10)]);
|
|
233
233
|
}
|
|
234
234
|
catch (err) {
|
|
235
|
-
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG
|
|
236
|
-
process.stderr.write(`[phren] link_findings docFragmentInsert: ${
|
|
235
|
+
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
|
|
236
|
+
process.stderr.write(`[phren] link_findings docFragmentInsert: ${errorMessage(err)}\n`);
|
|
237
237
|
}
|
|
238
238
|
const docFragmentResult = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [sourceDoc, "document"]);
|
|
239
239
|
if (!docFragmentResult?.length || !docFragmentResult[0]?.values?.length) {
|
|
@@ -245,8 +245,8 @@ export function register(server, ctx) {
|
|
|
245
245
|
db.run("INSERT OR IGNORE INTO entity_links (source_id, target_id, rel_type, source_doc) VALUES (?, ?, ?, ?)", [sourceId, targetId, relType, sourceDoc]);
|
|
246
246
|
}
|
|
247
247
|
catch (err) {
|
|
248
|
-
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG
|
|
249
|
-
process.stderr.write(`[phren] link_findings linkInsert: ${
|
|
248
|
+
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
|
|
249
|
+
process.stderr.write(`[phren] link_findings linkInsert: ${errorMessage(err)}\n`);
|
|
250
250
|
return mcpResponse({ ok: false, error: "Failed to insert fragment link." });
|
|
251
251
|
}
|
|
252
252
|
// 4a. Also populate global_entities so manual links appear in cross_project_fragments
|
|
@@ -255,8 +255,8 @@ export function register(server, ctx) {
|
|
|
255
255
|
db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [fragmentName, project, sourceDoc]);
|
|
256
256
|
}
|
|
257
257
|
catch (err) {
|
|
258
|
-
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG
|
|
259
|
-
process.stderr.write(`[phren] link_findings globalFragments: ${
|
|
258
|
+
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
|
|
259
|
+
process.stderr.write(`[phren] link_findings globalFragments: ${errorMessage(err)}\n`);
|
|
260
260
|
}
|
|
261
261
|
// 4b. Persist manual link so it survives index rebuilds (mandatory — failure aborts the operation)
|
|
262
262
|
const manualLinksPath = runtimeFile(ctx.phrenPath, "manual-links.json");
|
|
@@ -268,8 +268,8 @@ export function register(server, ctx) {
|
|
|
268
268
|
existing = JSON.parse(fs.readFileSync(manualLinksPath, "utf8"));
|
|
269
269
|
}
|
|
270
270
|
catch (err) {
|
|
271
|
-
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG
|
|
272
|
-
process.stderr.write(`[phren] link_findings manualLinksRead: ${
|
|
271
|
+
if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
|
|
272
|
+
process.stderr.write(`[phren] link_findings manualLinksRead: ${errorMessage(err)}\n`);
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
const newEntry = { entity: fragmentName, entityType: resolvedFragmentType, sourceDoc, relType };
|
package/mcp/dist/mcp-ops.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as fs from "fs";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { runtimeFile, getProjectDirs } from "./shared.js";
|
|
6
6
|
import { findFtsCacheForPath } from "./shared-index.js";
|
|
7
|
-
import { isValidProjectName } from "./utils.js";
|
|
7
|
+
import { isValidProjectName, errorMessage } from "./utils.js";
|
|
8
8
|
import { readReviewQueue, readReviewQueueAcrossProjects } from "./data-access.js";
|
|
9
9
|
import { addProjectFromPath } from "./core-project.js";
|
|
10
10
|
import { PROJECT_OWNERSHIP_MODES, parseProjectOwnershipMode } from "./project-config.js";
|
|
@@ -44,7 +44,7 @@ export function register(server, ctx) {
|
|
|
44
44
|
catch (err) {
|
|
45
45
|
return mcpResponse({
|
|
46
46
|
ok: false,
|
|
47
|
-
error:
|
|
47
|
+
error: errorMessage(err),
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
});
|
|
@@ -108,8 +108,8 @@ export function register(server, ctx) {
|
|
|
108
108
|
version = pkg.version || "unknown";
|
|
109
109
|
}
|
|
110
110
|
catch (err) {
|
|
111
|
-
if ((process.env.PHREN_DEBUG
|
|
112
|
-
process.stderr.write(`[phren] healthCheck version: ${
|
|
111
|
+
if ((process.env.PHREN_DEBUG))
|
|
112
|
+
process.stderr.write(`[phren] healthCheck version: ${errorMessage(err)}\n`);
|
|
113
113
|
}
|
|
114
114
|
// FTS index (lives in /tmpphren-fts-*/, not .runtime/)
|
|
115
115
|
let indexStatus = { exists: false };
|
|
@@ -117,8 +117,8 @@ export function register(server, ctx) {
|
|
|
117
117
|
indexStatus = findFtsCacheForPath(phrenPath, activeProfile);
|
|
118
118
|
}
|
|
119
119
|
catch (err) {
|
|
120
|
-
if ((process.env.PHREN_DEBUG
|
|
121
|
-
process.stderr.write(`[phren] healthCheck ftsCacheCheck: ${
|
|
120
|
+
if ((process.env.PHREN_DEBUG))
|
|
121
|
+
process.stderr.write(`[phren] healthCheck ftsCacheCheck: ${errorMessage(err)}\n`);
|
|
122
122
|
}
|
|
123
123
|
// Hook registration
|
|
124
124
|
let hooksEnabled = false;
|
|
@@ -127,8 +127,8 @@ export function register(server, ctx) {
|
|
|
127
127
|
hooksEnabled = getHooksEnabledPreference(phrenPath);
|
|
128
128
|
}
|
|
129
129
|
catch (err) {
|
|
130
|
-
if ((process.env.PHREN_DEBUG
|
|
131
|
-
process.stderr.write(`[phren] healthCheck hooksEnabled: ${
|
|
130
|
+
if ((process.env.PHREN_DEBUG))
|
|
131
|
+
process.stderr.write(`[phren] healthCheck hooksEnabled: ${errorMessage(err)}\n`);
|
|
132
132
|
}
|
|
133
133
|
let mcpEnabled = false;
|
|
134
134
|
try {
|
|
@@ -136,8 +136,8 @@ export function register(server, ctx) {
|
|
|
136
136
|
mcpEnabled = getMcpEnabledPreference(phrenPath);
|
|
137
137
|
}
|
|
138
138
|
catch (err) {
|
|
139
|
-
if ((process.env.PHREN_DEBUG
|
|
140
|
-
process.stderr.write(`[phren] healthCheck mcpEnabled: ${
|
|
139
|
+
if ((process.env.PHREN_DEBUG))
|
|
140
|
+
process.stderr.write(`[phren] healthCheck mcpEnabled: ${errorMessage(err)}\n`);
|
|
141
141
|
}
|
|
142
142
|
// Profile/machine info
|
|
143
143
|
const machineName = (() => {
|
|
@@ -145,8 +145,8 @@ export function register(server, ctx) {
|
|
|
145
145
|
return getMachineName();
|
|
146
146
|
}
|
|
147
147
|
catch (err) {
|
|
148
|
-
if ((process.env.PHREN_DEBUG
|
|
149
|
-
process.stderr.write(`[phren] healthCheck machineName: ${
|
|
148
|
+
if ((process.env.PHREN_DEBUG))
|
|
149
|
+
process.stderr.write(`[phren] healthCheck machineName: ${errorMessage(err)}\n`);
|
|
150
150
|
}
|
|
151
151
|
return undefined;
|
|
152
152
|
})();
|
|
@@ -160,8 +160,8 @@ export function register(server, ctx) {
|
|
|
160
160
|
taskMode = workflowPolicy.taskMode;
|
|
161
161
|
}
|
|
162
162
|
catch (err) {
|
|
163
|
-
if ((process.env.PHREN_DEBUG
|
|
164
|
-
process.stderr.write(`[phren] healthCheck taskMode: ${
|
|
163
|
+
if ((process.env.PHREN_DEBUG))
|
|
164
|
+
process.stderr.write(`[phren] healthCheck taskMode: ${errorMessage(err)}\n`);
|
|
165
165
|
}
|
|
166
166
|
try {
|
|
167
167
|
const { readInstallPreferences } = await import("./init-preferences.js");
|
|
@@ -169,8 +169,8 @@ export function register(server, ctx) {
|
|
|
169
169
|
proactivity = prefs.proactivity || "high";
|
|
170
170
|
}
|
|
171
171
|
catch (err) {
|
|
172
|
-
if ((process.env.PHREN_DEBUG
|
|
173
|
-
process.stderr.write(`[phren] healthCheck proactivity: ${
|
|
172
|
+
if ((process.env.PHREN_DEBUG))
|
|
173
|
+
process.stderr.write(`[phren] healthCheck proactivity: ${errorMessage(err)}\n`);
|
|
174
174
|
}
|
|
175
175
|
const lines = [
|
|
176
176
|
`Phren v${version}`,
|
|
@@ -262,8 +262,8 @@ export function register(server, ctx) {
|
|
|
262
262
|
return lines.filter(line => ERROR_PATTERNS.some(p => p.test(line)));
|
|
263
263
|
}
|
|
264
264
|
catch (err) {
|
|
265
|
-
if ((process.env.PHREN_DEBUG
|
|
266
|
-
process.stderr.write(`[phren] readErrorLines: ${
|
|
265
|
+
if ((process.env.PHREN_DEBUG))
|
|
266
|
+
process.stderr.write(`[phren] readErrorLines: ${errorMessage(err)}\n`);
|
|
267
267
|
return [];
|
|
268
268
|
}
|
|
269
269
|
}
|
package/mcp/dist/mcp-search.js
CHANGED
|
@@ -35,8 +35,8 @@ export function logSearchMiss(phrenPath, query, project) {
|
|
|
35
35
|
fs.appendFileSync(missFile, entry + "\n");
|
|
36
36
|
}
|
|
37
37
|
catch (err) {
|
|
38
|
-
if ((process.env.PHREN_DEBUG
|
|
39
|
-
process.stderr.write(`[phren] logSearchMiss: ${
|
|
38
|
+
if ((process.env.PHREN_DEBUG))
|
|
39
|
+
process.stderr.write(`[phren] logSearchMiss: ${errorMessage(err)}\n`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
const HISTORY_FINDING_STATUSES = new Set(["superseded", "retracted"]);
|
|
@@ -160,7 +160,7 @@ export function register(server, ctx) {
|
|
|
160
160
|
createdAt = stat.birthtime.toISOString();
|
|
161
161
|
}
|
|
162
162
|
catch (err) {
|
|
163
|
-
if ((process.env.PHREN_DEBUG
|
|
163
|
+
if ((process.env.PHREN_DEBUG))
|
|
164
164
|
process.stderr.write(`[phren] search_knowledge statFile: ${errorMessage(err)}\n`);
|
|
165
165
|
}
|
|
166
166
|
// Extract tags from content (e.g. [decision], [pitfall], [pattern])
|
|
@@ -393,8 +393,8 @@ export function register(server, ctx) {
|
|
|
393
393
|
relatedFragments = [...new Set(relatedFragments)].slice(0, 10);
|
|
394
394
|
}
|
|
395
395
|
catch (err) {
|
|
396
|
-
if ((process.env.PHREN_DEBUG
|
|
397
|
-
process.stderr.write(`[phren] fragment query: ${
|
|
396
|
+
if ((process.env.PHREN_DEBUG))
|
|
397
|
+
process.stderr.write(`[phren] fragment query: ${errorMessage(err)}\n`);
|
|
398
398
|
}
|
|
399
399
|
const formatted = results.map((r) => `### ${r.project}/${r.filename} (${r.type})\n${r.snippet}\n\n\`${r.path}\``);
|
|
400
400
|
// Memory synthesis: generate a concise paragraph from top results when requested
|
|
@@ -408,7 +408,7 @@ export function register(server, ctx) {
|
|
|
408
408
|
synthCache = JSON.parse(fs.readFileSync(synthCachePath, "utf8"));
|
|
409
409
|
}
|
|
410
410
|
catch (err) {
|
|
411
|
-
if ((process.env.PHREN_DEBUG
|
|
411
|
+
if ((process.env.PHREN_DEBUG))
|
|
412
412
|
process.stderr.write(`[phren] search_knowledge synthCacheRead: ${errorMessage(err)}\n`);
|
|
413
413
|
}
|
|
414
414
|
const cached = synthCache[synthKey];
|
|
@@ -433,8 +433,8 @@ export function register(server, ctx) {
|
|
|
433
433
|
fs.writeFileSync(synthCachePath, JSON.stringify(synthCache));
|
|
434
434
|
}
|
|
435
435
|
catch (err) {
|
|
436
|
-
if ((process.env.PHREN_DEBUG
|
|
437
|
-
process.stderr.write(`[phren] synthCache write: ${
|
|
436
|
+
if ((process.env.PHREN_DEBUG))
|
|
437
|
+
process.stderr.write(`[phren] synthCache write: ${errorMessage(err)}\n`);
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
440
|
}
|
|
@@ -267,6 +267,17 @@ ${TASK_UI_STYLES}
|
|
|
267
267
|
<div id="tab-settings" class="tab-content">
|
|
268
268
|
<div class="settings-shell">
|
|
269
269
|
<div id="settings-status-inline" class="settings-status-inline" aria-live="polite"></div>
|
|
270
|
+
<section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--cyan) 45%, var(--border))">
|
|
271
|
+
<div class="settings-section-header" style="display:flex;align-items:center;justify-content:space-between;gap:16px">
|
|
272
|
+
<span>Scope</span>
|
|
273
|
+
<select id="settings-project-select" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm);font-family:var(--font)">
|
|
274
|
+
<option value="">Global (all projects)</option>
|
|
275
|
+
</select>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="settings-section-body" style="padding:12px 18px">
|
|
278
|
+
<div id="settings-scope-note" style="font-size:var(--text-sm);color:var(--muted)">Showing global settings. Select a project to view and edit per-project overrides.</div>
|
|
279
|
+
</div>
|
|
280
|
+
</section>
|
|
270
281
|
<section class="settings-section settings-section-findings">
|
|
271
282
|
<div class="settings-section-header">Findings</div>
|
|
272
283
|
<div class="settings-section-body">
|
|
@@ -279,6 +290,18 @@ ${TASK_UI_STYLES}
|
|
|
279
290
|
<div id="settings-behavior" style="color:var(--muted)">Loading...</div>
|
|
280
291
|
</div>
|
|
281
292
|
</section>
|
|
293
|
+
<section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--warning) 45%, var(--border))">
|
|
294
|
+
<div class="settings-section-header">Retention Policy</div>
|
|
295
|
+
<div class="settings-section-body">
|
|
296
|
+
<div id="settings-retention" style="color:var(--muted)">Loading...</div>
|
|
297
|
+
</div>
|
|
298
|
+
</section>
|
|
299
|
+
<section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--success) 45%, var(--border))">
|
|
300
|
+
<div class="settings-section-header">Workflow Policy</div>
|
|
301
|
+
<div class="settings-section-body">
|
|
302
|
+
<div id="settings-workflow" style="color:var(--muted)">Loading...</div>
|
|
303
|
+
</div>
|
|
304
|
+
</section>
|
|
282
305
|
<section class="settings-section settings-section-integrations">
|
|
283
306
|
<div class="settings-section-header">Integrations</div>
|
|
284
307
|
<div class="settings-section-body">
|
|
@@ -839,14 +839,95 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
839
839
|
});
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
+
function getSettingsProject() {
|
|
843
|
+
var sel = document.getElementById('settings-project-select');
|
|
844
|
+
return sel ? sel.value : '';
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function postProjectOverride(project, field, value, clearField) {
|
|
848
|
+
var csrfUrl = _tsAuthToken ? tsAuthUrl('/api/csrf-token') : '/api/csrf-token';
|
|
849
|
+
fetch(csrfUrl).then(function(r) { return r.json(); }).then(function(csrfData) {
|
|
850
|
+
var payload = { project: project, field: field, value: value || '', clear: clearField ? 'true' : 'false' };
|
|
851
|
+
var body = new URLSearchParams(payload);
|
|
852
|
+
if (csrfData.token) body.set('_csrf', csrfData.token);
|
|
853
|
+
var url = _tsAuthToken ? tsAuthUrl('/api/settings/project-overrides') : '/api/settings/project-overrides';
|
|
854
|
+
return fetch(url, { method: 'POST', body: body, headers: { 'content-type': 'application/x-www-form-urlencoded' } });
|
|
855
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
856
|
+
if (!data.ok) {
|
|
857
|
+
setSettingsStatus(data.error || 'Failed to update project override', 'err');
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
_settingsLoaded = false;
|
|
861
|
+
loadSettings();
|
|
862
|
+
setSettingsStatus('Project override updated', 'ok');
|
|
863
|
+
}).catch(function(err) {
|
|
864
|
+
setSettingsStatus('Failed: ' + String(err), 'err');
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
842
868
|
function loadSettings() {
|
|
843
|
-
var
|
|
869
|
+
var selectedProject = getSettingsProject();
|
|
870
|
+
var baseUrl = '/api/settings';
|
|
871
|
+
if (selectedProject) baseUrl += '?project=' + encodeURIComponent(selectedProject);
|
|
872
|
+
var url = _tsAuthToken ? tsAuthUrl(baseUrl) : baseUrl;
|
|
873
|
+
|
|
874
|
+
// Populate project selector on first load
|
|
875
|
+
var sel = document.getElementById('settings-project-select');
|
|
876
|
+
if (sel && sel.querySelectorAll('option[data-proj]').length === 0) {
|
|
877
|
+
var configUrl = _tsAuthToken ? tsAuthUrl('/api/config') : '/api/config';
|
|
878
|
+
fetch(configUrl).then(function(r) { return r.json(); }).then(function(d) {
|
|
879
|
+
if (d.ok && d.projects && d.projects.length && sel) {
|
|
880
|
+
d.projects.forEach(function(p) {
|
|
881
|
+
var opt = document.createElement('option');
|
|
882
|
+
opt.value = p; opt.textContent = p;
|
|
883
|
+
opt.setAttribute('data-proj', '1');
|
|
884
|
+
sel.appendChild(opt);
|
|
885
|
+
});
|
|
886
|
+
if (selectedProject) sel.value = selectedProject;
|
|
887
|
+
}
|
|
888
|
+
}).catch(function() {});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
var scopeNote = document.getElementById('settings-scope-note');
|
|
892
|
+
if (scopeNote) {
|
|
893
|
+
scopeNote.textContent = selectedProject
|
|
894
|
+
? 'Showing effective config for "' + selectedProject + '". Overrides are saved to that project\'s phren.project.yaml.'
|
|
895
|
+
: 'Showing global settings. Select a project to view and edit per-project overrides.';
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Wire onChange once
|
|
899
|
+
if (sel && !sel.getAttribute('data-onchange-wired')) {
|
|
900
|
+
sel.setAttribute('data-onchange-wired', '1');
|
|
901
|
+
sel.addEventListener('change', function() {
|
|
902
|
+
_settingsLoaded = false;
|
|
903
|
+
loadSettings();
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
844
907
|
fetch(url).then(function(r) { return r.json(); }).then(function(data) {
|
|
845
908
|
if (!data.ok) {
|
|
846
909
|
setSettingsStatus(data.error || 'Failed to load settings', 'err');
|
|
847
910
|
return;
|
|
848
911
|
}
|
|
849
912
|
|
|
913
|
+
// Use merged config when a project is selected, else global
|
|
914
|
+
var effective = (selectedProject && data.merged) ? data.merged : null;
|
|
915
|
+
var rawOverrides = (selectedProject && data.overrides) ? data.overrides : null;
|
|
916
|
+
var effectiveSensitivity = effective ? effective.findingSensitivity : (data.findingSensitivity || 'balanced');
|
|
917
|
+
var effectiveTaskMode = effective ? effective.taskMode : (data.taskMode || 'auto');
|
|
918
|
+
var effectiveProactivity = data.proactivity || 'high';
|
|
919
|
+
var effectiveRetention = (effective && effective.retentionPolicy) ? effective.retentionPolicy : (data.retentionPolicy || {});
|
|
920
|
+
var effectiveWorkflow = (effective && effective.workflowPolicy) ? effective.workflowPolicy : (data.workflowPolicy || {});
|
|
921
|
+
|
|
922
|
+
var isProject = Boolean(selectedProject);
|
|
923
|
+
|
|
924
|
+
function sourceBadge(isOverride) {
|
|
925
|
+
if (!isProject) return '';
|
|
926
|
+
return isOverride
|
|
927
|
+
? '<span style="font-size:10px;font-weight:600;color:var(--warning);margin-left:6px;padding:1px 6px;border:1px solid color-mix(in srgb,var(--warning) 40%,transparent);border-radius:var(--radius-sm)">project override</span>'
|
|
928
|
+
: '<span style="font-size:10px;color:var(--text-muted);margin-left:6px;padding:1px 6px;border:1px solid var(--border);border-radius:var(--radius-sm)">global default</span>';
|
|
929
|
+
}
|
|
930
|
+
|
|
850
931
|
var findingDescriptions = {
|
|
851
932
|
high: 'Capture findings proactively, including minor observations.',
|
|
852
933
|
medium: 'Capture findings that are likely useful.',
|
|
@@ -856,56 +937,129 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
856
937
|
|
|
857
938
|
var findingsEl = document.getElementById('settings-findings');
|
|
858
939
|
if (findingsEl) {
|
|
859
|
-
var fsUi = findingStorageToUi(
|
|
940
|
+
var fsUi = findingStorageToUi(effectiveSensitivity);
|
|
860
941
|
var findingsHtml = '';
|
|
942
|
+
var fsSensOverride = rawOverrides && rawOverrides.findingSensitivity != null;
|
|
861
943
|
findingsHtml += '<div class="settings-control">';
|
|
862
|
-
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Finding sensitivity</span
|
|
944
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Finding sensitivity</span>' + sourceBadge(fsSensOverride);
|
|
945
|
+
if (isProject && fsSensOverride) {
|
|
946
|
+
findingsHtml += '<button data-ts-action="clearProjectOverride" data-field="findingSensitivity" class="settings-chip" style="font-size:11px;margin-left:auto">Clear override</button>';
|
|
947
|
+
}
|
|
948
|
+
findingsHtml += '</div>';
|
|
863
949
|
findingsHtml += '<div class="settings-chip-row">';
|
|
864
950
|
['high', 'medium', 'low', 'minimal'].forEach(function(level) {
|
|
865
951
|
var active = level === fsUi ? ' active' : '';
|
|
866
|
-
|
|
952
|
+
var action = isProject ? 'setProjectFindingSensitivity' : 'setFindingSensitivity';
|
|
953
|
+
findingsHtml += '<button data-ts-action="' + action + '" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
867
954
|
});
|
|
868
955
|
findingsHtml += '</div>';
|
|
869
956
|
findingsHtml += '<div class="settings-control-note" id="settings-fs-desc">' + esc(findingDescriptions[fsUi] || '') + '</div>';
|
|
870
957
|
findingsHtml += '</div>';
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
958
|
+
if (!isProject) {
|
|
959
|
+
findingsHtml += '<div class="settings-control">';
|
|
960
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Auto-capture</span>';
|
|
961
|
+
findingsHtml += '<button data-ts-action="toggleAutoCapture" data-enabled="' + (data.autoCaptureEnabled ? 'true' : 'false') + '" class="settings-chip' + (data.autoCaptureEnabled ? ' active' : '') + '">' + (data.autoCaptureEnabled ? 'On' : 'Off') + '</button></div>';
|
|
962
|
+
findingsHtml += '<div class="settings-control-note">Turn automatic finding capture on or off.</div>';
|
|
963
|
+
findingsHtml += '</div>';
|
|
964
|
+
findingsHtml += '<div class="settings-control">';
|
|
965
|
+
findingsHtml += '<div class="settings-control-header"><span class="settings-control-label">Consolidation threshold</span><span class="badge">' + esc(String(data.consolidationEntryThreshold || 25)) + ' entries</span></div>';
|
|
966
|
+
findingsHtml += '<div class="settings-control-note">Consolidation is also recommended after 60+ days with at least 10 new entries.</div>';
|
|
967
|
+
findingsHtml += '</div>';
|
|
968
|
+
}
|
|
880
969
|
findingsEl.innerHTML = findingsHtml;
|
|
881
970
|
}
|
|
882
971
|
|
|
883
972
|
var behaviorEl = document.getElementById('settings-behavior');
|
|
884
973
|
if (behaviorEl) {
|
|
885
|
-
var taskMode =
|
|
886
|
-
var proactivity =
|
|
974
|
+
var taskMode = effectiveTaskMode || 'auto';
|
|
975
|
+
var proactivity = effectiveProactivity;
|
|
887
976
|
var behaviorHtml = '';
|
|
977
|
+
var taskModeOverride = rawOverrides && rawOverrides.taskMode != null;
|
|
888
978
|
behaviorHtml += '<div class="settings-control">';
|
|
889
|
-
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Task mode</span
|
|
979
|
+
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Task mode</span>' + sourceBadge(taskModeOverride);
|
|
980
|
+
if (isProject && taskModeOverride) {
|
|
981
|
+
behaviorHtml += '<button data-ts-action="clearProjectOverride" data-field="taskMode" class="settings-chip" style="font-size:11px;margin-left:auto">Clear override</button>';
|
|
982
|
+
}
|
|
983
|
+
behaviorHtml += '</div>';
|
|
890
984
|
behaviorHtml += '<div class="settings-chip-row">';
|
|
891
|
-
['auto', 'manual', 'off'].forEach(function(mode) {
|
|
985
|
+
['auto', 'suggest', 'manual', 'off'].forEach(function(mode) {
|
|
892
986
|
var active = mode === taskMode ? ' active' : '';
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
behaviorHtml += '</div></div>';
|
|
896
|
-
behaviorHtml += '<div class="settings-control">';
|
|
897
|
-
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Proactivity level</span></div>';
|
|
898
|
-
behaviorHtml += '<div class="settings-chip-row">';
|
|
899
|
-
['high', 'medium', 'low'].forEach(function(level) {
|
|
900
|
-
var active = level === proactivity ? ' active' : '';
|
|
901
|
-
behaviorHtml += '<button data-ts-action="setProactivity" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
987
|
+
var action = isProject ? 'setProjectTaskMode' : 'setTaskMode';
|
|
988
|
+
behaviorHtml += '<button data-ts-action="' + action + '" data-mode="' + esc(mode) + '" class="settings-chip' + active + '">' + esc(mode) + '</button>';
|
|
902
989
|
});
|
|
903
990
|
behaviorHtml += '</div></div>';
|
|
991
|
+
if (!isProject) {
|
|
992
|
+
behaviorHtml += '<div class="settings-control">';
|
|
993
|
+
behaviorHtml += '<div class="settings-control-header"><span class="settings-control-label">Proactivity level</span></div>';
|
|
994
|
+
behaviorHtml += '<div class="settings-chip-row">';
|
|
995
|
+
['high', 'medium', 'low'].forEach(function(level) {
|
|
996
|
+
var active = level === proactivity ? ' active' : '';
|
|
997
|
+
behaviorHtml += '<button data-ts-action="setProactivity" data-level="' + esc(level) + '" class="settings-chip' + active + '">' + esc(level) + '</button>';
|
|
998
|
+
});
|
|
999
|
+
behaviorHtml += '</div></div>';
|
|
1000
|
+
}
|
|
904
1001
|
behaviorEl.innerHTML = behaviorHtml;
|
|
905
1002
|
}
|
|
906
1003
|
|
|
1004
|
+
var retentionEl = document.getElementById('settings-retention');
|
|
1005
|
+
if (retentionEl) {
|
|
1006
|
+
var ret = effectiveRetention;
|
|
1007
|
+
var retHtml = '';
|
|
1008
|
+
function retRow(label, field, value, note) {
|
|
1009
|
+
var isOverride = isProject && rawOverrides && rawOverrides.retentionPolicy && rawOverrides.retentionPolicy[field] !== undefined;
|
|
1010
|
+
retHtml += '<div class="settings-control">';
|
|
1011
|
+
retHtml += '<div class="settings-control-header"><span class="settings-control-label">' + esc(label) + '</span>' + sourceBadge(isOverride);
|
|
1012
|
+
retHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(String(value != null ? value : '—')) + '</span>';
|
|
1013
|
+
if (isProject && isOverride) {
|
|
1014
|
+
retHtml += '<button data-ts-action="clearProjectOverride" data-field="' + esc(field) + '" class="settings-chip" style="font-size:11px">Clear</button>';
|
|
1015
|
+
}
|
|
1016
|
+
retHtml += '</div>';
|
|
1017
|
+
if (note) retHtml += '<div class="settings-control-note">' + esc(note) + '</div>';
|
|
1018
|
+
if (isProject) {
|
|
1019
|
+
retHtml += '<div style="display:flex;gap:8px;align-items:center;margin-top:8px">' +
|
|
1020
|
+
'<input type="number" id="ret-input-' + esc(field) + '" value="' + esc(String(value != null ? value : '')) + '" style="width:100px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 8px;font-size:var(--text-sm);background:var(--surface);color:var(--ink)">' +
|
|
1021
|
+
'<button data-ts-action="setProjectRetention" data-field="' + esc(field) + '" class="settings-chip active" style="font-size:11px">Set</button>' +
|
|
1022
|
+
'</div>';
|
|
1023
|
+
}
|
|
1024
|
+
retHtml += '</div>';
|
|
1025
|
+
}
|
|
1026
|
+
retRow('TTL days', 'ttlDays', ret.ttlDays, 'Memories older than this are eligible for pruning.');
|
|
1027
|
+
retRow('Retention days', 'retentionDays', ret.retentionDays, 'Hard cutoff — memories past this age are removed.');
|
|
1028
|
+
retRow('Auto-accept threshold', 'autoAcceptThreshold', ret.autoAcceptThreshold, 'Confidence score (0–1) above which memories are auto-accepted.');
|
|
1029
|
+
retRow('Min inject confidence', 'minInjectConfidence', ret.minInjectConfidence, 'Minimum confidence score to inject a memory into context.');
|
|
1030
|
+
retentionEl.innerHTML = retHtml;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
var workflowEl = document.getElementById('settings-workflow');
|
|
1034
|
+
if (workflowEl) {
|
|
1035
|
+
var wf = effectiveWorkflow;
|
|
1036
|
+
var wfHtml = '';
|
|
1037
|
+
var lctOverride = isProject && rawOverrides && rawOverrides.workflowPolicy && rawOverrides.workflowPolicy.lowConfidenceThreshold !== undefined;
|
|
1038
|
+
var riskySectionsOverride = isProject && rawOverrides && rawOverrides.workflowPolicy && Array.isArray(rawOverrides.workflowPolicy.riskySections) && rawOverrides.workflowPolicy.riskySections.length > 0;
|
|
1039
|
+
wfHtml += '<div class="settings-control">';
|
|
1040
|
+
wfHtml += '<div class="settings-control-header"><span class="settings-control-label">Low confidence threshold</span>' + sourceBadge(lctOverride);
|
|
1041
|
+
wfHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(String(wf.lowConfidenceThreshold != null ? wf.lowConfidenceThreshold : '—')) + '</span>';
|
|
1042
|
+
if (isProject && lctOverride) {
|
|
1043
|
+
wfHtml += '<button data-ts-action="clearProjectOverride" data-field="lowConfidenceThreshold" class="settings-chip" style="font-size:11px">Clear</button>';
|
|
1044
|
+
}
|
|
1045
|
+
if (isProject) {
|
|
1046
|
+
wfHtml += '</div><div style="display:flex;gap:8px;align-items:center;margin-top:8px">' +
|
|
1047
|
+
'<input type="number" id="wf-input-lowConfidenceThreshold" min="0" max="1" step="0.05" value="' + esc(String(wf.lowConfidenceThreshold != null ? wf.lowConfidenceThreshold : '')) + '" style="width:100px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 8px;font-size:var(--text-sm);background:var(--surface);color:var(--ink)">' +
|
|
1048
|
+
'<button data-ts-action="setProjectWorkflow" data-field="lowConfidenceThreshold" class="settings-chip active" style="font-size:11px">Set</button>' +
|
|
1049
|
+
'</div>';
|
|
1050
|
+
} else {
|
|
1051
|
+
wfHtml += '</div>';
|
|
1052
|
+
}
|
|
1053
|
+
wfHtml += '<div class="settings-control-note">Memories below this confidence score are flagged for review.</div></div>';
|
|
1054
|
+
wfHtml += '<div class="settings-control">';
|
|
1055
|
+
wfHtml += '<div class="settings-control-header"><span class="settings-control-label">Risky sections</span>' + sourceBadge(riskySectionsOverride);
|
|
1056
|
+
wfHtml += '<span class="settings-control-value" style="margin-left:auto">' + esc(Array.isArray(wf.riskySections) ? wf.riskySections.join(', ') : '—') + '</span></div>';
|
|
1057
|
+
wfHtml += '<div class="settings-control-note">Sections that trigger approval gates when memories are written.</div></div>';
|
|
1058
|
+
workflowEl.innerHTML = wfHtml;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
907
1061
|
var integrationsEl = document.getElementById('settings-integrations');
|
|
908
|
-
if (integrationsEl) {
|
|
1062
|
+
if (integrationsEl && !isProject) {
|
|
909
1063
|
var tools = Array.isArray(data.hookTools) ? data.hookTools : [];
|
|
910
1064
|
var html = '';
|
|
911
1065
|
html += '<div class="settings-control-header" style="margin-bottom:10px"><span class="settings-control-label">Global MCP</span>';
|
|
@@ -923,6 +1077,8 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
923
1077
|
});
|
|
924
1078
|
html += '</tbody></table>';
|
|
925
1079
|
integrationsEl.innerHTML = html;
|
|
1080
|
+
} else if (integrationsEl && isProject) {
|
|
1081
|
+
integrationsEl.innerHTML = '<div style="color:var(--muted);font-size:var(--text-sm)">Integration settings are global — switch to Global scope to edit them.</div>';
|
|
926
1082
|
}
|
|
927
1083
|
}).catch(function(err) {
|
|
928
1084
|
setSettingsStatus('Failed to load settings: ' + String(err), 'err');
|
|
@@ -1063,6 +1219,33 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
1063
1219
|
else if (action === 'toggleIntegrationTool') { toggleIntegrationTool(actionEl.getAttribute('data-tool')); }
|
|
1064
1220
|
else if (action === 'showSessionDetail') { showSessionDetail(actionEl.getAttribute('data-session-id')); }
|
|
1065
1221
|
else if (action === 'backToSessionsList') { backToSessionsList(); }
|
|
1222
|
+
else if (action === 'setProjectFindingSensitivity') {
|
|
1223
|
+
var proj = getSettingsProject();
|
|
1224
|
+
var level = actionEl.getAttribute('data-level');
|
|
1225
|
+
postProjectOverride(proj, 'findingSensitivity', findingUiToStorage(level || 'medium'), false);
|
|
1226
|
+
}
|
|
1227
|
+
else if (action === 'setProjectTaskMode') {
|
|
1228
|
+
var proj = getSettingsProject();
|
|
1229
|
+
postProjectOverride(proj, 'taskMode', actionEl.getAttribute('data-mode') || 'auto', false);
|
|
1230
|
+
}
|
|
1231
|
+
else if (action === 'clearProjectOverride') {
|
|
1232
|
+
var proj = getSettingsProject();
|
|
1233
|
+
postProjectOverride(proj, actionEl.getAttribute('data-field') || '', '', true);
|
|
1234
|
+
}
|
|
1235
|
+
else if (action === 'setProjectRetention') {
|
|
1236
|
+
var proj = getSettingsProject();
|
|
1237
|
+
var field = actionEl.getAttribute('data-field') || '';
|
|
1238
|
+
var inputEl = document.getElementById('ret-input-' + field);
|
|
1239
|
+
var val = inputEl ? inputEl.value : '';
|
|
1240
|
+
postProjectOverride(proj, field, val, false);
|
|
1241
|
+
}
|
|
1242
|
+
else if (action === 'setProjectWorkflow') {
|
|
1243
|
+
var proj = getSettingsProject();
|
|
1244
|
+
var field = actionEl.getAttribute('data-field') || '';
|
|
1245
|
+
var inputEl = document.getElementById('wf-input-' + field);
|
|
1246
|
+
var val = inputEl ? inputEl.value : '';
|
|
1247
|
+
postProjectOverride(proj, field, val, false);
|
|
1248
|
+
}
|
|
1066
1249
|
});
|
|
1067
1250
|
|
|
1068
1251
|
window.setFindingSensitivity = function(level) {
|