odai-dsh-agent 0.2.0 → 0.2.2
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/LICENSE +21 -21
- package/README.md +138 -134
- package/bin/odai-dsh-agent.mjs +108 -108
- package/package.json +69 -69
- package/preset/odai/agent.cordis.yml +260 -260
- package/preset/odai/preset.yml +3 -3
- package/preset/odai/runtime/compaction-config.mjs +388 -388
- package/preset/odai/runtime/context-activation.mjs +71 -0
- package/preset/odai/runtime/context-capability.mjs +86 -0
- package/preset/odai/runtime/governance.mjs +128 -121
- package/preset/odai/runtime/human-care.mjs +45 -0
- package/preset/odai/runtime/human-safety-continuity.mjs +2 -2
- package/preset/odai/runtime/human-safety.mjs +3 -3
- package/preset/odai/runtime/index.mjs +2461 -1977
- package/preset/odai/runtime/output-config.mjs +360 -304
- package/preset/odai/runtime/research-packet.mjs +150 -150
- package/preset/odai/runtime/responsibility-gap.mjs +8 -10
- package/preset/odai/runtime/responsibility-scope.mjs +199 -0
- package/preset/odai/runtime/role-overlays.mjs +27 -27
- package/preset/odai/runtime/route-card.mjs +168 -152
- package/preset/odai/runtime/router.mjs +683 -651
- package/preset/odai/runtime/routing-config.mjs +480 -436
- package/preset/odai/runtime/routing-context.mjs +321 -321
- package/preset/odai/runtime/semantic-memory-store.mjs +452 -452
- package/preset/odai/runtime/semantic-memory.mjs +842 -842
- package/preset/odai/runtime/session-compat.mjs +389 -389
- package/preset/odai/runtime/skill-bundle.mjs +318 -318
- package/preset/odai/runtime/skill-evolution.mjs +1413 -1413
- package/preset/odai/skills/odai/SKILL.md +69 -68
- package/preset/odai/skills/odai/agents/openai.yaml +4 -4
- package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -11
- package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -11
- package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -5
- package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -13
- package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -24
- package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -15
- package/preset/odai/skills/odai/assets/routing-roles/executor.md +11 -11
- package/preset/odai/skills/odai/assets/routing-roles/frontend.md +7 -7
- package/preset/odai/skills/odai/assets/routing-roles/planner.md +29 -29
- package/preset/odai/skills/odai/assets/routing-roles/researcher.md +6 -6
- package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -11
- package/preset/odai/skills/odai/assets/task-state.md +14 -14
- package/preset/odai/skills/odai/manifest.json +37 -36
- package/preset/odai/skills/odai/references/care.md +26 -0
- package/preset/odai/skills/odai/references/craft.md +54 -54
- package/preset/odai/skills/odai/references/dao.md +30 -30
- package/preset/odai/skills/odai/references/human-safety.md +33 -54
- package/preset/odai/skills/odai/references/leverage.md +53 -53
- package/preset/odai/skills/odai/references/planning.md +82 -82
- package/preset/odai/skills/odai/references/support.md +36 -36
- package/preset/odai/skills/odai/references/verification.md +23 -23
- package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -273
- package/preset/odai/skills/odai/scripts/build-routing.mjs +274 -274
- package/preset/odai/skills/odai/scripts/install-routing.mjs +397 -397
- package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -457
- package/preset/odai/skills/odai/scripts/run-role.mjs +336 -336
- package/preset/odai/skills/odai/scripts/run-routing.mjs +1154 -1154
- package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -103
- package/scripts/verify-agent-load.mjs +257 -257
- package/src/dsh-version.mjs +6 -2
- package/src/installer.mjs +382 -382
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const ODAI_CONTEXTUAL_TOOL_NAMES = Object.freeze([
|
|
2
|
+
"odai_routing_config",
|
|
3
|
+
"odai_human_care",
|
|
4
|
+
"odai_human_safety",
|
|
5
|
+
"odai_route_card",
|
|
6
|
+
"odai_skill_source_config",
|
|
7
|
+
"odai_skill_evolution",
|
|
8
|
+
"odai_output_config",
|
|
9
|
+
"odai_compaction_config",
|
|
10
|
+
"odai_memory",
|
|
11
|
+
"odai_human_safety_continuity",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const ODAI_CORE_TOOL_NAMES = Object.freeze(["odai_context_capability", "odai_responsibility_gap"]);
|
|
15
|
+
|
|
16
|
+
const CARE_CUE = /(?:疲惫|心累|焦虑|紧张|自我怀疑|怀疑自己|否定自己|内耗|反刍|反复纠结|羞耻|丢脸|害怕犯错|怕犯错|持续消极|提不起劲|失去行动感|阿岱|欧黛|fatigue|burn(?:ed|t) out|anxi(?:ous|ety)|self[- ]doubt|doubt myself|ruminat(?:e|ing|ion)|shame|fear of mistakes?|persistent negativity|cannot get started|companionship style|practical support style)/iu;
|
|
17
|
+
const CRISIS_CUE = /(?:持续低落|越来越低落|绝望|无望|撑不住|活不下去|不想活|想死|结束生命|成为负担|自伤|自残|轻生|自杀|伤害自己|immediate danger|persistent low mood|hopeless|cannot go on|burden|self[- ]harm|suicid(?:e|al)|want to die|kill myself|end my life)/iu;
|
|
18
|
+
const ROUTING_CONFIG_CUE = /(?:(?:研究|调查|规划|计划|执行|审查|验收|前端|researcher|planner|executor|reviewer|frontend).{0,24}(?:模型|路由|映射|provider|model|route|mapping)|(?:模型|路由|映射|provider|model|route|mapping).{0,24}(?:研究|调查|规划|计划|执行|审查|验收|前端|researcher|planner|executor|reviewer|frontend)|(?:所有|当前|职责|责任)?(?:模型|路由)(?:配置|映射)|routing config)/iu;
|
|
19
|
+
const SKILL_SOURCE_CUE = /(?:(?:odai|阿岱|欧黛).{0,20}(?:skill|治理).{0,20}(?:来源|source|bundled|auto|user)|(?:skill|治理).{0,20}(?:来源|source).{0,20}(?:odai|阿岱|欧黛))/iu;
|
|
20
|
+
const SKILL_EVOLUTION_CUE = /(?:(?:odai|skill|治理).{0,24}(?:演化|代际|generation|evolution|rebase|rollback|activate|deactivate)|(?:演化|代际|generation|evolution|rebase|rollback).{0,24}(?:odai|skill|治理))/iu;
|
|
21
|
+
const OUTPUT_CONFIG_CUE = /(?:(?:输出|回复|回答).{0,20}(?:normal|正常|简洁|精简|concise|economy|经济|token|上限)|(?:maxTokens|token 上限|output mode|输出模式))/iu;
|
|
22
|
+
const COMPACTION_CONFIG_CUE = /(?:(?:压缩|compaction).{0,32}(?:模型|provider|model|推理|reasoning|配置|设置|查看|重置)|(?:模型|provider|model).{0,20}(?:压缩摘要|compaction summary))/iu;
|
|
23
|
+
const MEMORY_CUE = /(?:记住|记忆|忘记|以后默认|今后默认|从现在起|所有项目|每个项目|长期使用|一直使用|偏好|remember|memory|forget|from now on|going forward|by default|all projects|always use|never use)/iu;
|
|
24
|
+
const CONTINUITY_CUE = /(?:(?:跨会话|安全|关怀|照护|支持).{0,24}(?:档案|记录|连续性|记住|保存|查看|显示|导出|更正|删除|清空)|(?:continuity|safety|care|support).{0,24}(?:record|remember|save|show|export|correct|remove|clear))/iu;
|
|
25
|
+
|
|
26
|
+
export function classifyContextActivation(text) {
|
|
27
|
+
const value = typeof text === "string" ? text : "";
|
|
28
|
+
const safety = CRISIS_CUE.test(value);
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
care: !safety && CARE_CUE.test(value),
|
|
31
|
+
safety,
|
|
32
|
+
routingConfig: ROUTING_CONFIG_CUE.test(value),
|
|
33
|
+
skillSource: SKILL_SOURCE_CUE.test(value),
|
|
34
|
+
skillEvolution: SKILL_EVOLUTION_CUE.test(value),
|
|
35
|
+
outputConfig: OUTPUT_CONFIG_CUE.test(value),
|
|
36
|
+
compactionConfig: COMPACTION_CONFIG_CUE.test(value),
|
|
37
|
+
memory: MEMORY_CUE.test(value),
|
|
38
|
+
continuity: CONTINUITY_CUE.test(value),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function activeOdaiToolNames(activation, options = {}) {
|
|
43
|
+
if (options.child) return Object.freeze([]);
|
|
44
|
+
const names = new Set(ODAI_CORE_TOOL_NAMES);
|
|
45
|
+
if (activation.routingConfig) names.add("odai_routing_config");
|
|
46
|
+
if (activation.care) names.add("odai_human_care");
|
|
47
|
+
if (activation.safety) names.add("odai_human_safety");
|
|
48
|
+
if (options.routeCard) names.add("odai_route_card");
|
|
49
|
+
if (activation.skillSource) names.add("odai_skill_source_config");
|
|
50
|
+
if (activation.skillEvolution) names.add("odai_skill_evolution");
|
|
51
|
+
if (activation.outputConfig) names.add("odai_output_config");
|
|
52
|
+
if (activation.compactionConfig) names.add("odai_compaction_config");
|
|
53
|
+
if (activation.memory) names.add("odai_memory");
|
|
54
|
+
if (activation.continuity) names.add("odai_human_safety_continuity");
|
|
55
|
+
return Object.freeze([...names]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function inactiveOdaiToolNames(activeNames) {
|
|
59
|
+
const active = new Set(activeNames);
|
|
60
|
+
return Object.freeze(ODAI_CONTEXTUAL_TOOL_NAMES.filter((name) => !active.has(name)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function estimateContextTokens(value) {
|
|
64
|
+
return Math.ceil(String(value ?? "").length / 4);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function estimateToolSchemaTokens(tools) {
|
|
68
|
+
if (!Array.isArray(tools) || tools.length === 0) return 0;
|
|
69
|
+
const schemas = tools.map(({ name, description, parameters }) => ({ name, description, parameters }));
|
|
70
|
+
return Math.ceil(JSON.stringify(schemas).length / 4) + 4;
|
|
71
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const ODAI_CONTEXT_CAPABILITIES = Object.freeze([
|
|
2
|
+
"routing-config",
|
|
3
|
+
"human-care",
|
|
4
|
+
"human-safety",
|
|
5
|
+
"skill-source",
|
|
6
|
+
"skill-evolution",
|
|
7
|
+
"output-config",
|
|
8
|
+
"compaction-config",
|
|
9
|
+
"memory",
|
|
10
|
+
"safety-continuity",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const CAPABILITY_FIELDS = Object.freeze({
|
|
14
|
+
"routing-config": "routingConfig",
|
|
15
|
+
"human-care": "care",
|
|
16
|
+
"human-safety": "safety",
|
|
17
|
+
"skill-source": "skillSource",
|
|
18
|
+
"skill-evolution": "skillEvolution",
|
|
19
|
+
"output-config": "outputConfig",
|
|
20
|
+
"compaction-config": "compactionConfig",
|
|
21
|
+
memory: "memory",
|
|
22
|
+
"safety-continuity": "continuity",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export function requestedContextCapabilities(events, turn) {
|
|
26
|
+
if (!Number.isSafeInteger(turn)) return Object.freeze([]);
|
|
27
|
+
const capabilities = new Set();
|
|
28
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
29
|
+
if (event?.type === "odai/context-capability-requested"
|
|
30
|
+
&& event.data?.turn === turn
|
|
31
|
+
&& ODAI_CONTEXT_CAPABILITIES.includes(event.data.capability)) {
|
|
32
|
+
capabilities.add(event.data.capability);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Object.freeze([...capabilities]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function activateRequestedCapabilities(activation, capabilities) {
|
|
39
|
+
const merged = { ...activation };
|
|
40
|
+
for (const capability of capabilities) {
|
|
41
|
+
const field = CAPABILITY_FIELDS[capability];
|
|
42
|
+
if (field) merged[field] = true;
|
|
43
|
+
}
|
|
44
|
+
if (merged.safety) merged.care = false;
|
|
45
|
+
return Object.freeze(merged);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createContextCapabilityTool(options = {}) {
|
|
49
|
+
const isChild = typeof options.isChild === "function" ? options.isChild : () => false;
|
|
50
|
+
const onRequested = typeof options.onRequested === "function" ? options.onRequested : () => {};
|
|
51
|
+
return {
|
|
52
|
+
name: "odai_context_capability",
|
|
53
|
+
description: "Request one Odai capability when its specialized tool is not currently visible. Use this only as a discovery fallback for routing configuration, non-crisis care, crisis safety, skill source/evolution, output, compaction, memory, or safety continuity. The real tool and its complete constraints appear on the next step; this request performs no configuration, persistence, diagnosis, or model switch.",
|
|
54
|
+
parameters: {
|
|
55
|
+
type: "object",
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
required: ["capability"],
|
|
58
|
+
properties: {
|
|
59
|
+
capability: { type: "string", enum: ODAI_CONTEXT_CAPABILITIES },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
output: {
|
|
63
|
+
schema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
additionalProperties: false,
|
|
66
|
+
required: ["capability", "status"],
|
|
67
|
+
properties: {
|
|
68
|
+
capability: { type: "string", enum: ODAI_CONTEXT_CAPABILITIES },
|
|
69
|
+
status: { type: "string", enum: ["available-next-step"] },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
render(_args, value) {
|
|
73
|
+
return [{ type: "text", text: `Odai ${value.capability} capability is available on the next step.` }];
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
execute(args, execution) {
|
|
77
|
+
if (!execution.agent) throw new Error("odai_context_capability requires an owning agent session");
|
|
78
|
+
if (isChild(execution.agent)) throw new Error("child agents may not request Odai controller capabilities");
|
|
79
|
+
if (!args || typeof args !== "object" || Array.isArray(args) || !ODAI_CONTEXT_CAPABILITIES.includes(args.capability)) {
|
|
80
|
+
throw new TypeError(`capability must be ${ODAI_CONTEXT_CAPABILITIES.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
onRequested(execution.agent, args.capability, execution);
|
|
83
|
+
return Promise.resolve({ capability: args.capability, status: "available-next-step" });
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,121 +1,128 @@
|
|
|
1
|
-
export const DEFAULT_CHILD_DENIED_TOOLS = Object.freeze([
|
|
2
|
-
"write",
|
|
3
|
-
"edit",
|
|
4
|
-
"str_replace_editor",
|
|
5
|
-
"bash",
|
|
6
|
-
"pwsh",
|
|
7
|
-
"ask_user_question",
|
|
8
|
-
"subagent",
|
|
9
|
-
"subagent_fork",
|
|
10
|
-
"subagent_codex",
|
|
11
|
-
"subagent_claude_code",
|
|
12
|
-
"send_message",
|
|
13
|
-
"interrupt_agent",
|
|
14
|
-
"list_agents",
|
|
15
|
-
"workflow",
|
|
16
|
-
"ralph",
|
|
17
|
-
"job_output",
|
|
18
|
-
"job_list",
|
|
19
|
-
"job_kill",
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
export const DEFAULT_PROTECTED_CONTROLLER_ALLOWED_TOOLS = Object.freeze([
|
|
23
|
-
"read",
|
|
24
|
-
"read_image",
|
|
25
|
-
"glob",
|
|
26
|
-
"grep",
|
|
27
|
-
"web_search",
|
|
28
|
-
"web_fetch",
|
|
29
|
-
"ask_user_question",
|
|
30
|
-
"get_goal",
|
|
31
|
-
"list_agents",
|
|
32
|
-
"job_output",
|
|
33
|
-
"job_list",
|
|
34
|
-
"skill",
|
|
35
|
-
"odai_route_card",
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
export function isSubagent(agent) {
|
|
39
|
-
const header = agent?.session?.header;
|
|
40
|
-
return header?.origin === "subagent"
|
|
41
|
-
|| (Number.isSafeInteger(header?.delegationDepth) && header.delegationDepth > 0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createChildToolGuard(options = {}) {
|
|
45
|
-
const denied = new Set([
|
|
46
|
-
...DEFAULT_CHILD_DENIED_TOOLS,
|
|
47
|
-
...(Array.isArray(options.additionalDeniedTools) ? options.additionalDeniedTools : []),
|
|
48
|
-
]);
|
|
49
|
-
const onDenied = typeof options.onDenied === "function" ? options.onDenied : () => {};
|
|
50
|
-
|
|
51
|
-
return (execution) => {
|
|
52
|
-
if (!isSubagent(execution?.agent)) return undefined;
|
|
53
|
-
if (!denied.has(execution?.name)) return undefined;
|
|
54
|
-
|
|
55
|
-
const reason = `ODAI_SUBAGENT_BOUNDARY: child agents may not execute ${execution.name}; return evidence to the controller instead.`;
|
|
56
|
-
onDenied(execution, reason);
|
|
57
|
-
return reason;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function activeRouteProtection(agent, recordedEvents = agent?.session?.events) {
|
|
62
|
-
if (isSubagent(agent)) return undefined;
|
|
63
|
-
const events = recordedEvents;
|
|
64
|
-
if (!Array.isArray(events)) return undefined;
|
|
65
|
-
|
|
66
|
-
let currentTurn;
|
|
67
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
68
|
-
const event = events[index];
|
|
69
|
-
if (event?.type !== "odai/route-decided") continue;
|
|
70
|
-
if (!Number.isSafeInteger(event.data?.turn)) return undefined;
|
|
71
|
-
currentTurn = event.data.turn;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
if (currentTurn === undefined) return undefined;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (event
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
1
|
+
export const DEFAULT_CHILD_DENIED_TOOLS = Object.freeze([
|
|
2
|
+
"write",
|
|
3
|
+
"edit",
|
|
4
|
+
"str_replace_editor",
|
|
5
|
+
"bash",
|
|
6
|
+
"pwsh",
|
|
7
|
+
"ask_user_question",
|
|
8
|
+
"subagent",
|
|
9
|
+
"subagent_fork",
|
|
10
|
+
"subagent_codex",
|
|
11
|
+
"subagent_claude_code",
|
|
12
|
+
"send_message",
|
|
13
|
+
"interrupt_agent",
|
|
14
|
+
"list_agents",
|
|
15
|
+
"workflow",
|
|
16
|
+
"ralph",
|
|
17
|
+
"job_output",
|
|
18
|
+
"job_list",
|
|
19
|
+
"job_kill",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_PROTECTED_CONTROLLER_ALLOWED_TOOLS = Object.freeze([
|
|
23
|
+
"read",
|
|
24
|
+
"read_image",
|
|
25
|
+
"glob",
|
|
26
|
+
"grep",
|
|
27
|
+
"web_search",
|
|
28
|
+
"web_fetch",
|
|
29
|
+
"ask_user_question",
|
|
30
|
+
"get_goal",
|
|
31
|
+
"list_agents",
|
|
32
|
+
"job_output",
|
|
33
|
+
"job_list",
|
|
34
|
+
"skill",
|
|
35
|
+
"odai_route_card",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export function isSubagent(agent) {
|
|
39
|
+
const header = agent?.session?.header;
|
|
40
|
+
return header?.origin === "subagent"
|
|
41
|
+
|| (Number.isSafeInteger(header?.delegationDepth) && header.delegationDepth > 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createChildToolGuard(options = {}) {
|
|
45
|
+
const denied = new Set([
|
|
46
|
+
...DEFAULT_CHILD_DENIED_TOOLS,
|
|
47
|
+
...(Array.isArray(options.additionalDeniedTools) ? options.additionalDeniedTools : []),
|
|
48
|
+
]);
|
|
49
|
+
const onDenied = typeof options.onDenied === "function" ? options.onDenied : () => {};
|
|
50
|
+
|
|
51
|
+
return (execution) => {
|
|
52
|
+
if (!isSubagent(execution?.agent)) return undefined;
|
|
53
|
+
if (!denied.has(execution?.name)) return undefined;
|
|
54
|
+
|
|
55
|
+
const reason = `ODAI_SUBAGENT_BOUNDARY: child agents may not execute ${execution.name}; return evidence to the controller instead.`;
|
|
56
|
+
onDenied(execution, reason);
|
|
57
|
+
return reason;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function activeRouteProtection(agent, recordedEvents = agent?.session?.events) {
|
|
62
|
+
if (isSubagent(agent)) return undefined;
|
|
63
|
+
const events = recordedEvents;
|
|
64
|
+
if (!Array.isArray(events)) return undefined;
|
|
65
|
+
|
|
66
|
+
let currentTurn;
|
|
67
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
68
|
+
const event = events[index];
|
|
69
|
+
if (event?.type !== "odai/route-decided") continue;
|
|
70
|
+
if (!Number.isSafeInteger(event.data?.turn)) return undefined;
|
|
71
|
+
currentTurn = event.data.turn;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (currentTurn === undefined) return undefined;
|
|
75
|
+
|
|
76
|
+
const releasedScopes = new Set();
|
|
77
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
78
|
+
const event = events[index];
|
|
79
|
+
if (event?.type === "odai/route-protection-released" && event.data?.turn === currentTurn) {
|
|
80
|
+
if (typeof event.data?.scopeId === "string") releasedScopes.add(event.data.scopeId);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (event?.type !== "odai/route-protection") continue;
|
|
84
|
+
if (event.data?.turn !== currentTurn || event.data?.mode !== "read-only") continue;
|
|
85
|
+
if (event.data?.scopeId && releasedScopes.has(event.data.scopeId)) continue;
|
|
86
|
+
return event.data;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createRouteProtectionGuard(options = {}) {
|
|
92
|
+
const allowed = new Set(DEFAULT_PROTECTED_CONTROLLER_ALLOWED_TOOLS);
|
|
93
|
+
for (const name of Array.isArray(options.additionalDeniedTools) ? options.additionalDeniedTools : []) {
|
|
94
|
+
allowed.delete(name);
|
|
95
|
+
}
|
|
96
|
+
const onDenied = typeof options.onDenied === "function" ? options.onDenied : () => {};
|
|
97
|
+
const protectionFor = typeof options.protectionFor === "function"
|
|
98
|
+
? options.protectionFor
|
|
99
|
+
: activeRouteProtection;
|
|
100
|
+
|
|
101
|
+
return (execution) => {
|
|
102
|
+
if (isSubagent(execution?.agent)) return undefined;
|
|
103
|
+
if (allowed.has(execution?.name)) return undefined;
|
|
104
|
+
const protection = protectionFor(execution?.agent);
|
|
105
|
+
if (!protection) return undefined;
|
|
106
|
+
|
|
107
|
+
const reasonCode = protection.reasonCode ?? "unresolved-high-impact-route";
|
|
108
|
+
const reason = `ODAI_HIGH_IMPACT_ROUTE_BLOCKED: controller may not execute ${execution.name} while ${reasonCode} remains unresolved; use read-only evidence and provide an actionable decision path.`;
|
|
109
|
+
onDenied(execution, reason);
|
|
110
|
+
return reason;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function summarizeToolResult(execution, result) {
|
|
115
|
+
const summary = {
|
|
116
|
+
callId: String(execution.callId),
|
|
117
|
+
rootCallId: String(execution.rootCallId),
|
|
118
|
+
tool: execution.name,
|
|
119
|
+
child: isSubagent(execution.agent),
|
|
120
|
+
isError: result.isError === true,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (result.isError === true && result.error?.code) {
|
|
124
|
+
summary.errorCode = String(result.error.code);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return summary;
|
|
128
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const HUMAN_CARE_REFERENCE_PATH = "references/care.md";
|
|
2
|
+
|
|
3
|
+
export function createHumanCareTool(options = {}) {
|
|
4
|
+
if (typeof options.contractFor !== "function") throw new TypeError("createHumanCareTool requires contractFor");
|
|
5
|
+
return {
|
|
6
|
+
name: "odai_human_care",
|
|
7
|
+
description: "Load Odai's non-crisis care and user-controlled interaction-style contract when the current message shows fatigue, anxiety, self-doubt, rumination, shame, fear of mistakes, negativity, or reduced agency. This does not diagnose, score, persist state, or change model routing.",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
additionalProperties: false,
|
|
11
|
+
properties: {},
|
|
12
|
+
},
|
|
13
|
+
output: {
|
|
14
|
+
schema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
required: ["scope", "userChannelOwner", "contract"],
|
|
18
|
+
properties: {
|
|
19
|
+
scope: { type: "string", enum: ["non-crisis-care"] },
|
|
20
|
+
userChannelOwner: { type: "string", enum: ["current-controller", "controller"] },
|
|
21
|
+
contract: { type: "string" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
render(_args, value) {
|
|
25
|
+
return [{
|
|
26
|
+
type: "text",
|
|
27
|
+
text: `Non-crisis care contract loaded; the ${value.userChannelOwner} owns the response.\n\n${value.contract}`,
|
|
28
|
+
}];
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
execute(args, execution) {
|
|
32
|
+
if (args === null || typeof args !== "object" || Array.isArray(args) || Object.keys(args).length > 0) {
|
|
33
|
+
throw new TypeError("odai_human_care accepts no arguments");
|
|
34
|
+
}
|
|
35
|
+
if (!execution.agent) throw new Error("odai_human_care requires an owning agent session");
|
|
36
|
+
const contract = options.contractFor(execution.agent);
|
|
37
|
+
if (typeof contract !== "string" || contract.trim() === "") throw new Error("Odai human-care contract is unavailable");
|
|
38
|
+
return Promise.resolve({
|
|
39
|
+
scope: "non-crisis-care",
|
|
40
|
+
userChannelOwner: options.isChild?.(execution.agent) ? "controller" : "current-controller",
|
|
41
|
+
contract: contract.trim(),
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -16,11 +16,11 @@ const REPLACE_CUE = /(?:更正|改成|替换|更新|correct|change|replace|updat
|
|
|
16
16
|
const REMOVE_CUE = /(?:删除|移除|忘记|撤回|不要再记|刚才那条|这条|delete|remove|forget|retract|that entry)/iu;
|
|
17
17
|
const CLEAR_CUE = /(?:清空|全部删除|彻底删除|删掉.{0,12}全部|clear|delete all|erase all)/iu;
|
|
18
18
|
const SAFETY_SCOPE_CUE = /(?:安全|关怀|照护|支持|心理|心情|跨会话|档案|连续性|safety|care|support|continuity)/iu;
|
|
19
|
-
const SAFETY_CONTENT_CUE = /(
|
|
19
|
+
const SAFETY_CONTENT_CUE = /(?:危机|心理健康|情绪|心情|压力|焦虑|怀疑自己|否定自己|自我怀疑|内耗|反刍|反复纠结|消极|提不起劲|失去行动感|低落|难受|绝望|无望|心累|心烦|崩溃|撑不住|自伤|自残|轻生|自杀|伤害自己|倾听|陪伴|安抚|说教|给我空间|阿岱|欧黛|陪伴风格|行动支撑|可信任的人|紧急服务|crisis|mental health|emotion(?:al)?|overwhelm(?:ed|ing)?|distress(?:ed)?|anxi(?:ous|ety)|self[- ]doubt|doubt myself|ruminat(?:e|ing|ion)|internal conflict|persistent negativity|negative thoughts?|cannot get started|companionship style|practical support style|depress(?:ed|ion)?|hopeless|suicid(?:e|al)|self[- ]harm|hurt myself|listen(?:ing)?|check[- ]?ins?|calm me|support me|give me space|trusted person|emergency services?)/iu;
|
|
20
20
|
const ORDINARY_PROJECT_CONTENT_CUE = /(?:项目|仓库|代码|工作|截止期|构建|依赖|包管理|配置|测试|发布|部署|接口|数据库|文件|\b(?:project|repository|repo|code|work|deadlines?|build|dependencies?|package managers?|configuration|config|tests?|deployment|release|api|database|files?|pnpm|npm|yarn)\b)/iu;
|
|
21
21
|
const TECHNICAL_OPERATIONAL_INSTRUCTION_CUE = /(?:(?:必须|需要|应当|应该|要|改用|使用|运行|配置|构建|部署|发布)[^。!?\n]{0,32}(?:pnpm|npm|yarn|项目|仓库|代码|依赖|配置|测试|接口|数据库|文件)|(?:pnpm|npm|yarn)[^。!?\n]{0,12}(?:必须|必需|强制)|\b(?:must|should|need to|use|run|configure|build|deploy|release)\b[^.!?\n]{0,32}\b(?:pnpm|npm|yarn|project|repository|repo|code|dependencies?|configuration|config|tests?|api|database|files?)\b|\b(?:pnpm|npm|yarn)\b[^.!?\n]{0,12}\b(?:required|mandatory)\b)/iu;
|
|
22
22
|
const PERSONAL_CARE_ACTION_CUE = /(?:倾听|陪伴|安抚|不要说教|少问|问我|听我|给我空间|支持我|可信任的人|紧急服务|listen(?:ing)?|check[- ]?ins?|give me space|support me|stay with me|ask me|calm me|do not lecture|trusted person|emergency services?)/iu;
|
|
23
|
-
const PROJECT_PERSONAL_CARE_LINK_CUE = /(?:(?:让我|使我|令我)[^。!?\n]{0,16}(
|
|
23
|
+
const PROJECT_PERSONAL_CARE_LINK_CUE = /(?:(?:让我|使我|令我)[^。!?\n]{0,16}(?:焦虑|怀疑自己|否定自己|自我怀疑|内耗|反刍|消极|提不起劲|低落|难受|心累|心烦|崩溃|撑不住|压力|情绪)|(?:当|如果)?我[^。!?\n]{0,16}(?:焦虑|怀疑自己|否定自己|自我怀疑|内耗|反刍|消极|提不起劲|低落|难受|心累|心烦|崩溃|撑不住|压力大|情绪不好)|(?:项目|代码|工作|截止期)[^。!?\n]{0,24}(?:让我|使我|令我)[^。!?\n]{0,12}(?:焦虑|怀疑自己|否定自己|自我怀疑|内耗|反刍|消极|提不起劲|低落|难受|心累|心烦|崩溃|撑不住|压力)|\b(?:when i am|i feel|makes? me|leaves? me)[^.!?\n]{0,24}\b(?:overwhelm(?:ed)?|distress(?:ed)?|anxi(?:ous|ety)|self[- ]doubt|doubt myself|ruminat(?:e|ing)|negative|cannot get started|depress(?:ed)?|hopeless)|\b(?:code reviews?|deployment incidents?|deadlines?|work|the project)[^.!?\n]{0,24}\b(?:overwhelm(?:s|ed)? me|makes? me anxious|makes? me doubt myself|keeps? me ruminating|distresses? me))/iu;
|
|
24
24
|
const DEICTIC_CUE = /(?:刚才|那条|这条|全部|that|this|all)/iu;
|
|
25
25
|
const FORBIDDEN_PRIVATE_VALUE = /(?:password|passwd|api[_ -]?key|access[_ -]?token|secret|密码|口令|密钥|令牌|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b|\b\+?\d[\d ()-]{8,}\d\b)/iu;
|
|
26
26
|
const MUTATION_REFUSAL_CUE = /(?:(?:不要|别|请勿|不许|禁止|无需|不用|不想|拒绝)[^,,;;::。!?\n]{0,20}(?:跨会话|记住|保存|存入|更正|改成|替换|更新)|(?:跨会话|记住|保存|存入|更正|改成|替换|更新)[^,,;;::。!?\n]{0,24}(?:取消|算了|不要(?:了|再?(?:记住|保存|存入|更正|改|替换|更新))|别再?(?:记住|保存|存入|更正|改|替换|更新))|\b(?:do not|don't|never|no need to|refuse to|without)\b[^,;:.!?\n]{0,48}\b(?:remember|save|store|persist|correct|change|replace|update)\b|\b(?:remember|save|store|persist|correct|change|replace|update)\b[^,;:.!?\n]{0,32}\b(?:cancel|never mind|(?:do not|don't)\s+(?:remember|save|store|persist|correct|change|replace|update))\b)/iu;
|
|
@@ -4,7 +4,7 @@ export function createHumanSafetyTool(options = {}) {
|
|
|
4
4
|
if (typeof options.contractFor !== "function") throw new TypeError("createHumanSafetyTool requires contractFor");
|
|
5
5
|
return {
|
|
6
6
|
name: "odai_human_safety",
|
|
7
|
-
description: "Load Odai's highest-priority
|
|
7
|
+
description: "Load Odai's highest-priority crisis-safety contract for sustained or worsening low mood, hopelessness, burden, self-harm, suicide, or immediate danger. Invoke proactively; do not diagnose, score, persist state, or change model routing.",
|
|
8
8
|
parameters: {
|
|
9
9
|
type: "object",
|
|
10
10
|
additionalProperties: false,
|
|
@@ -19,7 +19,7 @@ export function createHumanSafetyTool(options = {}) {
|
|
|
19
19
|
priority: { type: "string", enum: ["highest"] },
|
|
20
20
|
principles: {
|
|
21
21
|
type: "array",
|
|
22
|
-
items: { type: "string", enum: ["
|
|
22
|
+
items: { type: "string", enum: ["timely-intervention", "active-guidance", "no-secondary-harm"] },
|
|
23
23
|
},
|
|
24
24
|
userChannelOwner: { type: "string", enum: ["current-controller", "controller"] },
|
|
25
25
|
contract: { type: "string" },
|
|
@@ -41,7 +41,7 @@ export function createHumanSafetyTool(options = {}) {
|
|
|
41
41
|
if (typeof contract !== "string" || contract.trim() === "") throw new Error("Odai human-safety contract is unavailable");
|
|
42
42
|
return Promise.resolve({
|
|
43
43
|
priority: "highest",
|
|
44
|
-
principles: ["
|
|
44
|
+
principles: ["timely-intervention", "active-guidance", "no-secondary-harm"],
|
|
45
45
|
userChannelOwner: options.isChild?.(execution.agent) ? "controller" : "current-controller",
|
|
46
46
|
contract: contract.trim(),
|
|
47
47
|
});
|