negotium 0.1.19 → 0.1.21
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/agent-helpers.js +146 -23
- package/dist/agent-helpers.js.map +14 -12
- package/dist/cron.js +137 -52
- package/dist/cron.js.map +20 -19
- package/dist/hosted-agent.js +29 -17
- package/dist/hosted-agent.js.map +7 -7
- package/dist/main.js +145 -55
- package/dist/main.js.map +20 -19
- package/dist/mcp-factories.js +23 -22
- package/dist/mcp-factories.js.map +3 -3
- package/dist/prompts.js +98 -9
- package/dist/prompts.js.map +6 -5
- package/dist/runtime/scripts/browser-passkey-policy.mjs +36 -0
- package/dist/runtime/scripts/browser-vault-transform.mjs +33 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +22 -4
- package/dist/runtime/src/agents/archiver.ts +0 -14
- package/dist/runtime/src/agents/claude-provider.ts +11 -7
- package/dist/runtime/src/agents/execution-host.ts +10 -1
- package/dist/runtime/src/agents/idle-archiver.ts +21 -0
- package/dist/runtime/src/agents/maestro-provider.ts +9 -14
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +1 -1
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +6 -1
- package/dist/runtime/src/agents/memory-archive-policy.ts +34 -0
- package/dist/runtime/src/agents/model-catalog.ts +84 -18
- package/dist/runtime/src/mcp/factories/vault.ts +36 -34
- package/dist/runtime/src/mcp/vault-server.ts +1 -0
- package/dist/runtime/src/platform/mcp-config.ts +4 -8
- package/dist/runtime/src/platform/playwright/manager.ts +5 -2
- package/dist/runtime/src/prompts/builders.ts +36 -7
- package/dist/runtime/src/runtime/turn-runner.ts +2 -0
- package/dist/runtime/src/storage/topic-archive.ts +5 -2
- package/dist/runtime/src/topics/lifecycle.ts +2 -1
- package/dist/runtime/src/topics/session.ts +2 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/storage.js +23 -3
- package/dist/storage.js.map +5 -4
- package/dist/types/packages/core/src/agents/claude-provider.d.ts +1 -0
- package/dist/types/packages/core/src/agents/execution-host.d.ts +2 -0
- package/dist/types/packages/core/src/agents/idle-archiver.d.ts +2 -0
- package/dist/types/packages/core/src/agents/memory-archive-policy.d.ts +14 -0
- package/dist/types/packages/core/src/agents/model-catalog.d.ts +77 -0
- package/dist/types/packages/core/src/mcp/factories/vault.d.ts +1 -0
- package/dist/types/packages/core/src/prompts/builders.d.ts +5 -1
- package/dist/types/packages/core/src/storage/topic-archive.d.ts +1 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +2 -2
package/dist/agent-helpers.js
CHANGED
|
@@ -1907,6 +1907,19 @@ function vaultListWithValues(userId) {
|
|
|
1907
1907
|
function vaultList(userId) {
|
|
1908
1908
|
return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
1909
1909
|
}
|
|
1910
|
+
function vaultSubstituteDetailed(userId, text) {
|
|
1911
|
+
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
1912
|
+
const usedKeys = new Set;
|
|
1913
|
+
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
1914
|
+
const key = normalizeVaultKey(rawKey);
|
|
1915
|
+
const value = entries.get(key);
|
|
1916
|
+
if (value === undefined)
|
|
1917
|
+
return match;
|
|
1918
|
+
usedKeys.add(key);
|
|
1919
|
+
return value;
|
|
1920
|
+
});
|
|
1921
|
+
return { text: substituted, usedKeys: [...usedKeys] };
|
|
1922
|
+
}
|
|
1910
1923
|
function valueReferencesVaultKey(userId, value) {
|
|
1911
1924
|
const keys = new Set(vaultList(userId).map((entry) => entry.key));
|
|
1912
1925
|
const visit = (candidate) => {
|
|
@@ -2533,9 +2546,7 @@ var MCP_CATALOG = {
|
|
|
2533
2546
|
vault: {
|
|
2534
2547
|
...commonRuntimeMcpPolicy("vault"),
|
|
2535
2548
|
build({ userId, agent }) {
|
|
2536
|
-
const args = [`--user-id=${userId}
|
|
2537
|
-
if (agent === "codex")
|
|
2538
|
-
args.push("--http-only=true");
|
|
2549
|
+
const args = [`--user-id=${userId}`, "--list-only=true"];
|
|
2539
2550
|
return buildStdioMcpServer(agent, VAULT_SERVER, args);
|
|
2540
2551
|
}
|
|
2541
2552
|
}
|
|
@@ -2757,6 +2768,7 @@ function getMcpServersForQuery(opts) {
|
|
|
2757
2768
|
var defaultHost = {
|
|
2758
2769
|
getMcpServersForQuery,
|
|
2759
2770
|
redactVaultSecrets,
|
|
2771
|
+
substituteVaultSecrets: (userId, value) => vaultSubstituteDetailed(userId, value).text,
|
|
2760
2772
|
referencesRuntimeSecretStorage,
|
|
2761
2773
|
shouldRedirectVaultTool,
|
|
2762
2774
|
claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
|
|
@@ -2779,12 +2791,12 @@ function hostedMcpServers(opts) {
|
|
|
2779
2791
|
function redactHostedSecrets(userId, value) {
|
|
2780
2792
|
return activeHost().redactVaultSecrets(userId, value);
|
|
2781
2793
|
}
|
|
2794
|
+
function substituteHostedSecrets(userId, value) {
|
|
2795
|
+
return activeHost().substituteVaultSecrets(userId, value);
|
|
2796
|
+
}
|
|
2782
2797
|
function referencesHostedSecretStorage(value) {
|
|
2783
2798
|
return activeHost().referencesRuntimeSecretStorage(value);
|
|
2784
2799
|
}
|
|
2785
|
-
function shouldRedirectHostedVaultTool(userId, toolName, input) {
|
|
2786
|
-
return activeHost().shouldRedirectVaultTool(userId, toolName, input);
|
|
2787
|
-
}
|
|
2788
2800
|
function hostedClaudeCodeExecutablePath() {
|
|
2789
2801
|
return activeHost().claudeCodeExecutablePath();
|
|
2790
2802
|
}
|
|
@@ -2836,6 +2848,9 @@ var CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
|
|
|
2836
2848
|
"TaskGet"
|
|
2837
2849
|
];
|
|
2838
2850
|
var CLAUDE_NATIVE_AGENT_TOOLS = ["Task", "Agent", "TaskOutput", "TaskStop"];
|
|
2851
|
+
function substituteClaudeToolInput(userId, input) {
|
|
2852
|
+
return deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
|
|
2853
|
+
}
|
|
2839
2854
|
function buildClaudeDisallowedTools(extra = undefined) {
|
|
2840
2855
|
return [
|
|
2841
2856
|
...new Set([
|
|
@@ -3074,14 +3089,14 @@ async function* claudeProvider(opts) {
|
|
|
3074
3089
|
};
|
|
3075
3090
|
}
|
|
3076
3091
|
const userId = opts.userId ?? "";
|
|
3077
|
-
|
|
3092
|
+
const withVault = substituteClaudeToolInput(userId, tool_input);
|
|
3093
|
+
if (JSON.stringify(withVault) === JSON.stringify(tool_input)) {
|
|
3078
3094
|
return { continue: true };
|
|
3079
3095
|
}
|
|
3080
3096
|
return {
|
|
3081
3097
|
hookSpecificOutput: {
|
|
3082
3098
|
hookEventName: "PreToolUse",
|
|
3083
|
-
|
|
3084
|
-
permissionDecisionReason: VAULT_BROKER_REDIRECT_ERROR
|
|
3099
|
+
updatedInput: withVault
|
|
3085
3100
|
}
|
|
3086
3101
|
};
|
|
3087
3102
|
}
|
|
@@ -3306,7 +3321,7 @@ import { createRequire } from "module";
|
|
|
3306
3321
|
import { dirname as dirname6, join as join11 } from "path";
|
|
3307
3322
|
|
|
3308
3323
|
// ../../packages/core/src/version.ts
|
|
3309
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3324
|
+
var NEGOTIUM_VERSION = "0.1.21";
|
|
3310
3325
|
|
|
3311
3326
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3312
3327
|
var NEGOTIUM_MODEL_CATALOG = "negotium-model-catalog.json";
|
|
@@ -3941,14 +3956,12 @@ function buildMaestroDisallowedTools(callerDisallowedTools = []) {
|
|
|
3941
3956
|
function buildVaultHook(userId) {
|
|
3942
3957
|
return {
|
|
3943
3958
|
name: "vault-guard",
|
|
3944
|
-
pre({
|
|
3959
|
+
pre({ input }) {
|
|
3945
3960
|
if (referencesHostedSecretStorage(input)) {
|
|
3946
3961
|
return { decision: "block", error: "Runtime secret storage access is not permitted" };
|
|
3947
3962
|
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
}
|
|
3951
|
-
return { decision: "allow" };
|
|
3963
|
+
const substituted = deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
|
|
3964
|
+
return JSON.stringify(substituted) === JSON.stringify(input) ? { decision: "allow" } : { decision: "modify", input: substituted };
|
|
3952
3965
|
},
|
|
3953
3966
|
post({ output }) {
|
|
3954
3967
|
const redacted = redactHostedSecrets(userId, output);
|
|
@@ -4405,6 +4418,86 @@ var WsHub = {
|
|
|
4405
4418
|
// ../../packages/core/src/prompts/builders.ts
|
|
4406
4419
|
import { readFileSync as readFileSync9 } from "fs";
|
|
4407
4420
|
import { resolve as resolve5 } from "path";
|
|
4421
|
+
|
|
4422
|
+
// ../../packages/core/src/agents/model-catalog.ts
|
|
4423
|
+
var CODEX_PRO_20X_COST = "ChatGPT Pro 20x subscription: $200/month";
|
|
4424
|
+
var CODEX_COMMUNITY_WEEKLY = "Community plan-level observation: roughly 2\u20134B raw/cached tokens per week; fresh-input equivalent is much lower and unstable (low confidence)";
|
|
4425
|
+
var CLAUDE_MAX_20X_COST = "Claude Max 20x subscription: $200/month";
|
|
4426
|
+
var CLAUDE_COMMUNITY_SESSION = "Community observations vary from roughly 220\u2013250K locally displayed tokens per 5-hour session to billions of cache-heavy raw tokens per week; calibrated reports value a full weekly allowance around $680\u2013$1,900 at API rates. Recent heavy-model reports reach the weekly cap after about 4\u20135 full sessions (low confidence; not a token cap)";
|
|
4427
|
+
var SELECTABLE_MODELS = [
|
|
4428
|
+
{
|
|
4429
|
+
model: "gpt-5.6-sol",
|
|
4430
|
+
agent: "codex",
|
|
4431
|
+
description: "Highest-capability Codex route for the hardest agentic coding work.",
|
|
4432
|
+
intelligenceTier: "fable",
|
|
4433
|
+
routingSummary: "hardest coding work; 5x Codex quota cost",
|
|
4434
|
+
accessCost: CODEX_PRO_20X_COST,
|
|
4435
|
+
marginalTokenCost: "Codex credits: $5/M uncached input, $0.50/M cached input, $30/M output",
|
|
4436
|
+
estimatedUsage: `Official Pro 20x range: 300\u20131,800 local messages per 5 hours; quota weight 5x Luna. ${CODEX_COMMUNITY_WEEKLY}`
|
|
4437
|
+
},
|
|
4438
|
+
{
|
|
4439
|
+
model: "gpt-5.6-terra",
|
|
4440
|
+
agent: "codex",
|
|
4441
|
+
description: "High-capability Codex route for complex coding and reasoning.",
|
|
4442
|
+
intelligenceTier: "opus",
|
|
4443
|
+
routingSummary: "complex coding and reasoning; 2.5x Codex quota cost",
|
|
4444
|
+
accessCost: CODEX_PRO_20X_COST,
|
|
4445
|
+
marginalTokenCost: "Codex credits: $2.50/M uncached input, $0.25/M cached input, $15/M output",
|
|
4446
|
+
estimatedUsage: `Official Pro 20x range: 400\u20132,200 local messages per 5 hours; quota weight 2.5x Luna. ${CODEX_COMMUNITY_WEEKLY}`
|
|
4447
|
+
},
|
|
4448
|
+
{
|
|
4449
|
+
model: "gpt-5.6-luna",
|
|
4450
|
+
agent: "codex",
|
|
4451
|
+
description: "Default Codex route with strong everyday coding intelligence.",
|
|
4452
|
+
intelligenceTier: "sonnet",
|
|
4453
|
+
routingSummary: "everyday coding default; lowest Codex quota cost (1x)",
|
|
4454
|
+
accessCost: CODEX_PRO_20X_COST,
|
|
4455
|
+
marginalTokenCost: "Codex credits: $1/M uncached input, $0.10/M cached input, $6/M output",
|
|
4456
|
+
estimatedUsage: `Official Pro 20x range: 1,000\u20135,600 local messages per 5 hours; lowest Codex quota weight (1x). ${CODEX_COMMUNITY_WEEKLY}`
|
|
4457
|
+
},
|
|
4458
|
+
{
|
|
4459
|
+
model: "fable",
|
|
4460
|
+
agent: "claude",
|
|
4461
|
+
description: "Highest-capability Claude route for the hardest and longest-running tasks.",
|
|
4462
|
+
intelligenceTier: "fable",
|
|
4463
|
+
routingSummary: "hardest long-running work; highest Claude cost; explicit request only",
|
|
4464
|
+
accessCost: CLAUDE_MAX_20X_COST,
|
|
4465
|
+
marginalTokenCost: "Claude API/extra usage: $10/M input, $12.50/M cache write, $1/M cache read, $50/M output",
|
|
4466
|
+
estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Fable drains weighted quota fastest, so use only on explicit user request. No stable per-model token cap is published.`
|
|
4467
|
+
},
|
|
4468
|
+
{
|
|
4469
|
+
model: "opus",
|
|
4470
|
+
agent: "claude",
|
|
4471
|
+
description: "High-capability Claude route for complex reasoning and tool-heavy work.",
|
|
4472
|
+
intelligenceTier: "opus",
|
|
4473
|
+
routingSummary: "complex reasoning and tool-heavy work; about 2.5x Sonnet marginal cost",
|
|
4474
|
+
accessCost: CLAUDE_MAX_20X_COST,
|
|
4475
|
+
marginalTokenCost: "Claude API/extra usage: $5/M input, $6.25/M cache write, $0.50/M cache read, $25/M output",
|
|
4476
|
+
estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Opus uses the shared all-model weekly pool more quickly than Sonnet. No stable per-model token cap is published.`
|
|
4477
|
+
},
|
|
4478
|
+
{
|
|
4479
|
+
model: "sonnet",
|
|
4480
|
+
agent: "claude",
|
|
4481
|
+
description: "Default Claude route for capable, efficient everyday work.",
|
|
4482
|
+
intelligenceTier: "sonnet",
|
|
4483
|
+
routingSummary: "capable everyday default; lowest Claude model cost",
|
|
4484
|
+
accessCost: CLAUDE_MAX_20X_COST,
|
|
4485
|
+
marginalTokenCost: "Claude API/extra usage introductory rate: $2/M input, $2.50/M cache write, $0.20/M cache read, $10/M output through 2026-08-31; then $3/M input and $15/M output",
|
|
4486
|
+
estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Sonnet also has a separate weekly allowance and normally provides the highest Claude throughput. No stable weekly token cap is published.`
|
|
4487
|
+
},
|
|
4488
|
+
{
|
|
4489
|
+
model: "deepseek-pro",
|
|
4490
|
+
agent: "maestro",
|
|
4491
|
+
description: "API-priced Sonnet-level route for cost-efficient everyday work.",
|
|
4492
|
+
intelligenceTier: "sonnet",
|
|
4493
|
+
routingSummary: "cost-efficient everyday work; pay-per-token and cheapest route",
|
|
4494
|
+
accessCost: "DeepSeek V4 Pro pay-as-you-go API; no monthly subscription required",
|
|
4495
|
+
marginalTokenCost: "DeepSeek API: $0.435/M uncached input, $0.003625/M cached input, $0.87/M output",
|
|
4496
|
+
estimatedUsage: "No subscription token cap; pay per token. Official account concurrency limit is 500 requests."
|
|
4497
|
+
}
|
|
4498
|
+
];
|
|
4499
|
+
|
|
4500
|
+
// ../../packages/core/src/prompts/builders.ts
|
|
4408
4501
|
var PROMPTS_DIR = resolve5(PROJECT_ROOT, "src/prompts");
|
|
4409
4502
|
var SESSIONS_DIR = resolve5(PROMPTS_DIR, "sessions");
|
|
4410
4503
|
function loadAgentPrompt(filename) {
|
|
@@ -5138,7 +5231,6 @@ function ensurePersonalGeneral(userId) {
|
|
|
5138
5231
|
|
|
5139
5232
|
// ../../packages/core/src/agents/archiver.ts
|
|
5140
5233
|
var MAX_BRIEF_ENTRIES = 8;
|
|
5141
|
-
var MIN_ARCHIVE_MESSAGES = 4;
|
|
5142
5234
|
var MAX_SESSION_STEPS = 20;
|
|
5143
5235
|
var activeArchiverSessions = new Map;
|
|
5144
5236
|
function boundedSessionText(value) {
|
|
@@ -5166,10 +5258,6 @@ function getArchiverDef() {
|
|
|
5166
5258
|
}
|
|
5167
5259
|
function runArchiverTurn(params) {
|
|
5168
5260
|
const { userId, topicId, topicTitle, archivePath, messageCount, mode = "deleted-topic" } = params;
|
|
5169
|
-
if (mode !== "active-topic" && messageCount < MIN_ARCHIVE_MESSAGES) {
|
|
5170
|
-
logger.info({ userId, topicTitle, messageCount, min: MIN_ARCHIVE_MESSAGES }, "archiver: skipped \u2014 too few messages to distil");
|
|
5171
|
-
return false;
|
|
5172
|
-
}
|
|
5173
5261
|
let archiverDef;
|
|
5174
5262
|
try {
|
|
5175
5263
|
archiverDef = getArchiverDef();
|
|
@@ -5378,6 +5466,25 @@ ${rolled.join(`
|
|
|
5378
5466
|
}
|
|
5379
5467
|
}
|
|
5380
5468
|
|
|
5469
|
+
// ../../packages/core/src/agents/memory-archive-policy.ts
|
|
5470
|
+
function countMemoryArchiveExchanges(rows) {
|
|
5471
|
+
let waitingForAssistant = false;
|
|
5472
|
+
let completed = 0;
|
|
5473
|
+
for (const row of rows) {
|
|
5474
|
+
const assistant = row.author_id === "ai" || Boolean(row.agent_type);
|
|
5475
|
+
const conversational = row.kind == null || row.kind === "message";
|
|
5476
|
+
if (!assistant && row.author_id !== "system" && conversational) {
|
|
5477
|
+
waitingForAssistant = true;
|
|
5478
|
+
continue;
|
|
5479
|
+
}
|
|
5480
|
+
if (assistant && conversational && waitingForAssistant) {
|
|
5481
|
+
completed++;
|
|
5482
|
+
waitingForAssistant = false;
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
return completed;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5381
5488
|
// ../../packages/core/src/storage/runtime-leases.ts
|
|
5382
5489
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
5383
5490
|
|
|
@@ -5867,8 +5974,9 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
|
|
|
5867
5974
|
counter++;
|
|
5868
5975
|
}
|
|
5869
5976
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
5977
|
+
const exchangeCount = countMemoryArchiveExchanges(rows);
|
|
5978
|
+
logger.info({ topicId, topicTitle, archive: path, messageCount: rows.length, exchangeCount, lastRowid }, "archiveTopicMessages: archived topic messages");
|
|
5979
|
+
return { path, messageCount: rows.length, exchangeCount, lastRowid };
|
|
5872
5980
|
}
|
|
5873
5981
|
|
|
5874
5982
|
// ../../packages/core/src/storage/topic-archive-state.ts
|
|
@@ -6065,6 +6173,7 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
6065
6173
|
if (!options.allowMentionOnly && topic.aiMode === "mention")
|
|
6066
6174
|
return "mention-only-channel";
|
|
6067
6175
|
let skipped = "empty";
|
|
6176
|
+
let skipMemoryTurn = false;
|
|
6068
6177
|
const claim = claimTopicArchiveJob(topicId, (afterRowid) => {
|
|
6069
6178
|
const pending = getMessagesForTopicAfterRowid(topicId, afterRowid);
|
|
6070
6179
|
const minMessages = options.minMessages;
|
|
@@ -6073,6 +6182,8 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
6073
6182
|
logger.debug({ topicId, topicTitle: topic.title, pending: pending.length, minMessages }, "topic-memory-archiver: skipped below threshold");
|
|
6074
6183
|
return null;
|
|
6075
6184
|
}
|
|
6185
|
+
const exchangeCount = countMemoryArchiveExchanges(pending);
|
|
6186
|
+
skipMemoryTurn = options.minExchanges !== undefined && exchangeCount < options.minExchanges;
|
|
6076
6187
|
const archived = (options.archiveMessages ?? archiveTopicMessages)(topicId, topic.title, {
|
|
6077
6188
|
afterRowid,
|
|
6078
6189
|
reason: options.reason
|
|
@@ -6090,6 +6201,18 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
6090
6201
|
if (claim.kind === "busy")
|
|
6091
6202
|
return "busy";
|
|
6092
6203
|
const { job } = claim;
|
|
6204
|
+
if (skipMemoryTurn) {
|
|
6205
|
+
settleTopicArchiveJob(topicId, job.archivePath, true);
|
|
6206
|
+
logger.info({
|
|
6207
|
+
topicId,
|
|
6208
|
+
topicTitle: topic.title,
|
|
6209
|
+
messageCount: job.messageCount,
|
|
6210
|
+
minExchanges: options.minExchanges,
|
|
6211
|
+
archive: job.archivePath,
|
|
6212
|
+
reason: options.reason
|
|
6213
|
+
}, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
|
|
6214
|
+
return "below-threshold";
|
|
6215
|
+
}
|
|
6093
6216
|
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
6094
6217
|
const launched = (options.launchArchiver ?? runArchiverTurn)({
|
|
6095
6218
|
userId,
|
|
@@ -6242,4 +6365,4 @@ export {
|
|
|
6242
6365
|
VAULT_BROKER_REDIRECT_ERROR
|
|
6243
6366
|
};
|
|
6244
6367
|
|
|
6245
|
-
//# debugId=
|
|
6368
|
+
//# debugId=AF4B0BAEC1BE82AE64756E2164756E21
|