pi-proactive 0.2.0-beta.2 → 0.2.0-beta.3
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/CHANGELOG.md +29 -0
- package/README.md +32 -28
- package/build/cli.mjs +121 -0
- package/build/extensions/proactive.js +47 -6
- package/build/src/commands.js +78 -2
- package/build/src/config.js +511 -4
- package/build/src/daemon-config.js +221 -0
- package/build/src/daemon-contact.js +103 -0
- package/build/src/daemon-host.js +84 -0
- package/build/src/daemon.js +193 -3
- package/build/src/health.js +84 -0
- package/build/src/lineage.js +41 -2
- package/build/src/notify.js +183 -0
- package/build/src/progress-eta.js +134 -0
- package/build/src/trigger.js +43 -0
- package/build/src/trust-loop.js +321 -0
- package/build/src/trust-metrics.js +236 -0
- package/build/src/trust-notify.js +73 -0
- package/build/src/trust-policy.js +58 -0
- package/build/src/trust-registry.js +323 -0
- package/daemon/launchd.plist +48 -0
- package/daemon/pi-proactive-daemon.service +29 -0
- package/daemon/run-daemon.sh +84 -0
- package/node_modules/typebox/build/compile/code.mjs +2 -1
- package/node_modules/typebox/build/compile/validator.mjs +1 -5
- package/node_modules/typebox/build/format/date_time.d.mts +1 -2
- package/node_modules/typebox/build/format/date_time.mjs +3 -12
- package/node_modules/typebox/build/format/duration.d.mts +2 -2
- package/node_modules/typebox/build/format/duration.mjs +2 -2
- package/node_modules/typebox/build/format/idn_email.d.mts +0 -1
- package/node_modules/typebox/build/format/idn_email.mjs +0 -1
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
- package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
- package/node_modules/typebox/build/format/ipv4.mjs +2 -2
- package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
- package/node_modules/typebox/build/format/ipv6.mjs +1 -1
- package/node_modules/typebox/build/format/iri.mjs +9 -16
- package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +5 -7
- package/node_modules/typebox/build/guard/guard.mjs +9 -14
- package/node_modules/typebox/build/guard/index.mjs +0 -6
- package/node_modules/typebox/build/guard/{unicode/unicode_segment.mjs → string.mjs} +19 -16
- package/node_modules/typebox/build/schema/build.mjs +5 -5
- package/node_modules/typebox/build/schema/check.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/_context.d.mts +1 -5
- package/node_modules/typebox/build/schema/engine/_context.mjs +3 -16
- package/node_modules/typebox/build/schema/engine/_functions.d.mts +2 -2
- package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/_reducer.d.mts +3 -3
- package/node_modules/typebox/build/schema/engine/_refine.d.mts +5 -5
- package/node_modules/typebox/build/schema/engine/_refine.mjs +9 -14
- package/node_modules/typebox/build/schema/engine/_stack.d.mts +20 -34
- package/node_modules/typebox/build/schema/engine/_stack.mjs +202 -143
- package/node_modules/typebox/build/schema/engine/_unique.d.mts +2 -4
- package/node_modules/typebox/build/schema/engine/_unique.mjs +3 -7
- package/node_modules/typebox/build/schema/engine/additionalItems.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +8 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +32 -27
- package/node_modules/typebox/build/schema/engine/allOf.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/allOf.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/anyOf.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/anyOf.mjs +9 -4
- package/node_modules/typebox/build/schema/engine/boolean.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/boolean.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/const.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/const.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/contains.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/contains.mjs +9 -5
- package/node_modules/typebox/build/schema/engine/dependencies.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +7 -5
- package/node_modules/typebox/build/schema/engine/dependentRequired.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/dependentRequired.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/dependentSchemas.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +1 -2
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +8 -12
- package/node_modules/typebox/build/schema/engine/enum.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/enum.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/exclusiveMaximum.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/exclusiveMaximum.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/exclusiveMinimum.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/exclusiveMinimum.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/format.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/format.mjs +8 -11
- package/node_modules/typebox/build/schema/engine/if.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/if.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/index.d.mts +0 -1
- package/node_modules/typebox/build/schema/engine/index.mjs +0 -1
- package/node_modules/typebox/build/schema/engine/items.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/items.mjs +5 -8
- package/node_modules/typebox/build/schema/engine/maxContains.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +8 -4
- package/node_modules/typebox/build/schema/engine/maxItems.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/maxItems.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/maxLength.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/maxLength.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/maxProperties.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/maxProperties.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/maximum.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/maximum.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/minContains.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/minContains.mjs +9 -5
- package/node_modules/typebox/build/schema/engine/minItems.d.mts +5 -5
- package/node_modules/typebox/build/schema/engine/minItems.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/minLength.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/minLength.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/minProperties.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/minProperties.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/minimum.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/minimum.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/multipleOf.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/multipleOf.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/not.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/not.mjs +8 -4
- package/node_modules/typebox/build/schema/engine/oneOf.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +11 -6
- package/node_modules/typebox/build/schema/engine/pattern.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/pattern.mjs +7 -3
- package/node_modules/typebox/build/schema/engine/patternProperties.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +5 -6
- package/node_modules/typebox/build/schema/engine/prefixItems.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/prefixItems.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/properties.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -4
- package/node_modules/typebox/build/schema/engine/propertyNames.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +10 -7
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +8 -12
- package/node_modules/typebox/build/schema/engine/ref.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/ref.mjs +14 -18
- package/node_modules/typebox/build/schema/engine/required.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/required.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/schema.d.mts +7 -7
- package/node_modules/typebox/build/schema/engine/schema.mjs +138 -136
- package/node_modules/typebox/build/schema/engine/type.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/type.mjs +6 -2
- package/node_modules/typebox/build/schema/engine/unevaluatedItems.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +9 -5
- package/node_modules/typebox/build/schema/engine/unevaluatedProperties.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +9 -5
- package/node_modules/typebox/build/schema/engine/uniqueItems.d.mts +4 -4
- package/node_modules/typebox/build/schema/engine/uniqueItems.mjs +6 -2
- package/node_modules/typebox/build/schema/errors.mjs +1 -1
- package/node_modules/typebox/build/schema/intern/intern.d.mts +9 -6
- package/node_modules/typebox/build/schema/intern/intern.mjs +56 -90
- package/node_modules/typebox/build/schema/parse.mjs +0 -3
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +6 -1
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +64 -47
- package/node_modules/typebox/build/schema/resolve/resolve.d.mts +32 -6
- package/node_modules/typebox/build/schema/resolve/resolve.mjs +227 -231
- package/node_modules/typebox/build/schema/schema.d.mts +0 -1
- package/node_modules/typebox/build/schema/schema.mjs +0 -1
- package/node_modules/typebox/build/system/hashing/hash.mjs +34 -63
- package/node_modules/typebox/build/system/locale/en_US.mjs +1 -2
- package/node_modules/typebox/build/system/memory/assign.d.mts +8 -2
- package/node_modules/typebox/build/system/memory/assign.mjs +6 -13
- package/node_modules/typebox/build/system/memory/clone.d.mts +5 -4
- package/node_modules/typebox/build/system/memory/clone.mjs +96 -8
- package/node_modules/typebox/build/system/memory/create.d.mts +5 -1
- package/node_modules/typebox/build/system/memory/create.mjs +13 -18
- package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
- package/node_modules/typebox/build/system/memory/discard.mjs +8 -9
- package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
- package/node_modules/typebox/build/system/memory/update.d.mts +4 -1
- package/node_modules/typebox/build/system/memory/update.mjs +16 -43
- package/node_modules/typebox/build/system/settings/settings.d.mts +0 -11
- package/node_modules/typebox/build/system/settings/settings.mjs +0 -14
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +24 -2
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +29 -2
- package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +0 -1
- package/node_modules/typebox/build/type/engine/evaluate/index.mjs +0 -1
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +1 -5
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +1 -6
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +3 -1
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +4 -1
- package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +3 -1
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +4 -1
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +3 -1
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +4 -1
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +2 -15
- package/node_modules/typebox/build/type/extends/tuple.d.mts +1 -2
- package/node_modules/typebox/build/type/extends/tuple.mjs +1 -21
- package/node_modules/typebox/build/value/clean/from_object.mjs +1 -1
- package/node_modules/typebox/build/value/clone/clone.d.mts +3 -4
- package/node_modules/typebox/build/value/clone/clone.mjs +7 -5
- package/node_modules/typebox/build/value/parse/parse.mjs +4 -10
- package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +14 -25
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +84 -52
- package/package.json +11 -3
- package/skills/pi-proactive/SKILL.md +27 -24
- package/tools/cli.mjs +121 -0
- package/tools/daemon-install.sh +397 -0
- package/tools/daemon-uninstall.sh +226 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +0 -6
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +0 -35
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +0 -1
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +0 -11
- package/node_modules/typebox/build/schema/meta/draft_2019_09.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_2019_09.mjs +0 -288
- package/node_modules/typebox/build/schema/meta/draft_2020_12.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_2020_12.mjs +0 -300
- package/node_modules/typebox/build/schema/meta/draft_3.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_3.mjs +0 -143
- package/node_modules/typebox/build/schema/meta/draft_4.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_4.mjs +0 -149
- package/node_modules/typebox/build/schema/meta/draft_6.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_6.mjs +0 -155
- package/node_modules/typebox/build/schema/meta/draft_7.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/draft_7.mjs +0 -172
- package/node_modules/typebox/build/schema/meta/index.d.mts +0 -2
- package/node_modules/typebox/build/schema/meta/index.mjs +0 -2
- package/node_modules/typebox/build/schema/meta/meta.d.mts +0 -9
- package/node_modules/typebox/build/schema/meta/meta.mjs +0 -15
- package/node_modules/typebox/build/schema/meta/schema.d.mts +0 -6
- package/node_modules/typebox/build/schema/meta/schema.mjs +0 -1
- package/node_modules/typebox/build/system/memory/deep.d.mts +0 -2
- package/node_modules/typebox/build/system/memory/deep.mjs +0 -108
- package/node_modules/typebox/build/system/memory/finalize.d.mts +0 -4
- package/node_modules/typebox/build/system/memory/finalize.mjs +0 -30
- package/node_modules/typebox/build/system/memory/keys.d.mts +0 -2
- package/node_modules/typebox/build/system/memory/keys.mjs +0 -8
- package/node_modules/typebox/build/system/settings/internal.d.mts +0 -2
- package/node_modules/typebox/build/system/settings/internal.mjs +0 -20
- package/node_modules/typebox/build/type/engine/call/index.d.mts +0 -1
- package/node_modules/typebox/build/type/engine/call/index.mjs +0 -1
- package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +0 -30
- package/node_modules/typebox/build/type/engine/evaluate/property.mjs +0 -29
- package/node_modules/typebox/build/type/engine/this/index.d.mts +0 -1
- package/node_modules/typebox/build/type/engine/this/index.mjs +0 -1
- package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +0 -2
- package/node_modules/typebox/build/value/repair/from_cyclic.mjs +0 -6
- /package/node_modules/typebox/build/guard/{unicode/unicode_segment.d.mts → string.d.mts} +0 -0
|
@@ -115,8 +115,26 @@ export function defaultDaemonConfig(agentId) {
|
|
|
115
115
|
handoverHour: 8,
|
|
116
116
|
staleSocketMs: 3_600_000,
|
|
117
117
|
staleExecutionMs: 3_600_000,
|
|
118
|
+
contact: { ...DEFAULT_CONTACT_CONFIG, payload: { ...DEFAULT_CONTACT_CONFIG.payload } },
|
|
119
|
+
host: { ...DEFAULT_HOST_CONFIG },
|
|
118
120
|
};
|
|
119
121
|
}
|
|
122
|
+
/** 主动联系的缺省配置(**频次保守**:默认 1 小时一次、每天 8 次封顶)。 */
|
|
123
|
+
export const DEFAULT_CONTACT_CONFIG = {
|
|
124
|
+
enabled: true,
|
|
125
|
+
target: "auto",
|
|
126
|
+
minIntervalMs: 3_600_000,
|
|
127
|
+
maxPerDay: 8,
|
|
128
|
+
deviceNotify: "fallback",
|
|
129
|
+
payload: {
|
|
130
|
+
why: "有事情需要你确认",
|
|
131
|
+
ask: "在会话里回一句就行;也可以先不管它",
|
|
132
|
+
consequence: "不处理 → 这件事会一直等着(**不会**自动替你决定)",
|
|
133
|
+
howToDisable: "运行 bash pi-proactive/tools/daemon-uninstall.sh,或把 daemon.json 的 contact.enabled 设为 false",
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
/** 宿主缺省(auto = 有 herdr 用 herdr,没有就回落系统宿主)。 */
|
|
137
|
+
export const DEFAULT_HOST_CONFIG = { hostKind: "auto" };
|
|
120
138
|
/** 配置文件路径:`<agentDir>/proactive/daemon.json`。 */
|
|
121
139
|
export function daemonConfigPath(agentDir) {
|
|
122
140
|
return path.join(proactiveDir(agentDir), "daemon.json");
|
|
@@ -136,12 +154,35 @@ function str(v, fallback) {
|
|
|
136
154
|
function tier(v, fallback) {
|
|
137
155
|
return v === "auto" || v === "limited" || v === "human-gated" ? v : fallback;
|
|
138
156
|
}
|
|
157
|
+
/** 合并主动联系配置(逐字段校验;坏字段回落缺省 —— **不得静默变成"没有上限"**)。 */
|
|
158
|
+
function mergeContactConfig(base, raw) {
|
|
159
|
+
const p = isPlainObject(raw.payload) ? raw.payload : {};
|
|
160
|
+
return {
|
|
161
|
+
enabled: bool(raw.enabled, base.enabled),
|
|
162
|
+
target: str(raw.target, base.target).trim() || base.target,
|
|
163
|
+
minIntervalMs: Math.max(0, num(raw.minIntervalMs, base.minIntervalMs)),
|
|
164
|
+
maxPerDay: Math.max(0, num(raw.maxPerDay, base.maxPerDay)),
|
|
165
|
+
deviceNotify: raw.deviceNotify === "always" || raw.deviceNotify === "fallback" ? raw.deviceNotify : base.deviceNotify,
|
|
166
|
+
payload: {
|
|
167
|
+
why: str(p.why, base.payload.why),
|
|
168
|
+
ask: str(p.ask, base.payload.ask),
|
|
169
|
+
consequence: str(p.consequence, base.payload.consequence),
|
|
170
|
+
howToDisable: str(p.howToDisable, base.payload.howToDisable),
|
|
171
|
+
...(p.title !== undefined ? { title: str(p.title, base.payload.title ?? "") } : {}),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
139
175
|
/** 把落盘的原始 JSON 合并进缺省配置(**逐字段**校验,坏字段回落缺省)。 */
|
|
140
176
|
export function mergeDaemonConfig(base, raw) {
|
|
141
177
|
if (!isPlainObject(raw))
|
|
142
178
|
return base;
|
|
143
179
|
const budget = isPlainObject(raw.budget) ? raw.budget : {};
|
|
144
180
|
const perms = isPlainObject(raw.permissions) ? raw.permissions : {};
|
|
181
|
+
const rawHost = isPlainObject(raw.host) ? raw.host : {};
|
|
182
|
+
// 宿主取值读取(**兼容旧键名**:`kind` 专指 DeliverKind → 宿主一律用 `hostKind`;
|
|
183
|
+
// 旧配置文件里写的 `host.kind` 仍要认,否则升级即丢用户配置)。
|
|
184
|
+
const rawHostKindValue = rawHost["hostKind"] ?? rawHost["kind"];
|
|
185
|
+
const rawHostKind = typeof rawHostKindValue === "string" ? rawHostKindValue : undefined;
|
|
145
186
|
const rawRules = Array.isArray(perms.rules) ? perms.rules : [];
|
|
146
187
|
const rules = [];
|
|
147
188
|
for (const item of rawRules) {
|
|
@@ -176,6 +217,12 @@ export function mergeDaemonConfig(base, raw) {
|
|
|
176
217
|
handoverHour: num(raw.handoverHour, base.handoverHour),
|
|
177
218
|
staleSocketMs: num(raw.staleSocketMs, base.staleSocketMs),
|
|
178
219
|
staleExecutionMs: num(raw.staleExecutionMs, base.staleExecutionMs),
|
|
220
|
+
contact: mergeContactConfig(base.contact, isPlainObject(raw.contact) ? raw.contact : {}),
|
|
221
|
+
host: {
|
|
222
|
+
hostKind: rawHostKind === "herdr" || rawHostKind === "system" || rawHostKind === "auto"
|
|
223
|
+
? rawHostKind
|
|
224
|
+
: base.host.hostKind,
|
|
225
|
+
},
|
|
179
226
|
};
|
|
180
227
|
}
|
|
181
228
|
/**
|
|
@@ -486,3 +533,177 @@ export function readLatestHandover(agentDir) {
|
|
|
486
533
|
}
|
|
487
534
|
return { path: file, date: name.replace(/\.md$/, ""), content };
|
|
488
535
|
}
|
|
536
|
+
/** 常驻目录:`<agentDir>/proactive/daemon`。 */
|
|
537
|
+
export function daemonDir(agentDir) {
|
|
538
|
+
return path.join(proactiveDir(agentDir), "daemon");
|
|
539
|
+
}
|
|
540
|
+
/** 心跳文件路径:`<agentDir>/proactive/daemon/heartbeat.json`(**外部可验证「它还活着」**)。 */
|
|
541
|
+
export function heartbeatPath(agentDir) {
|
|
542
|
+
return path.join(daemonDir(agentDir), "heartbeat.json");
|
|
543
|
+
}
|
|
544
|
+
/** 宿主状态文件路径:`<agentDir>/proactive/daemon/host.json`(herdr 宿主时记 workspace/pane)。 */
|
|
545
|
+
export function hostStatePath(agentDir) {
|
|
546
|
+
return path.join(daemonDir(agentDir), "host.json");
|
|
547
|
+
}
|
|
548
|
+
/** 主动联系清单路径:`<agentDir>/proactive/daemon/contacts.jsonl`(JSONL,一行一条)。 */
|
|
549
|
+
export function contactLedgerPath(agentDir) {
|
|
550
|
+
return path.join(daemonDir(agentDir), "contacts.jsonl");
|
|
551
|
+
}
|
|
552
|
+
/** 读清单(**坏行只丢那一行**,不整体失败;坏文件也不会抛)。 */
|
|
553
|
+
export function readContactEntries(agentDir) {
|
|
554
|
+
const file = contactLedgerPath(agentDir);
|
|
555
|
+
let raw;
|
|
556
|
+
try {
|
|
557
|
+
raw = fs.readFileSync(file, "utf8");
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
const out = [];
|
|
563
|
+
for (const line of raw.split("\n")) {
|
|
564
|
+
const text = line.trim();
|
|
565
|
+
if (text === "")
|
|
566
|
+
continue;
|
|
567
|
+
let parsed;
|
|
568
|
+
try {
|
|
569
|
+
parsed = JSON.parse(text);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (!isPlainObject(parsed))
|
|
575
|
+
continue;
|
|
576
|
+
if (typeof parsed.eventId !== "string" || parsed.eventId === "")
|
|
577
|
+
continue;
|
|
578
|
+
if (typeof parsed.ts !== "number")
|
|
579
|
+
continue;
|
|
580
|
+
const p = isPlainObject(parsed.payload) ? parsed.payload : {};
|
|
581
|
+
out.push({
|
|
582
|
+
eventId: parsed.eventId,
|
|
583
|
+
ts: parsed.ts,
|
|
584
|
+
outcome: parsed.outcome ?? "session",
|
|
585
|
+
channels: Array.isArray(parsed.channels) ? parsed.channels.filter((c) => typeof c === "string") : [],
|
|
586
|
+
...(typeof parsed.targetAgentId === "string" ? { targetAgentId: parsed.targetAgentId } : {}),
|
|
587
|
+
payload: {
|
|
588
|
+
why: str(p.why, ""),
|
|
589
|
+
ask: str(p.ask, ""),
|
|
590
|
+
consequence: str(p.consequence, ""),
|
|
591
|
+
howToDisable: str(p.howToDisable, ""),
|
|
592
|
+
...(typeof p.title === "string" ? { title: p.title } : {}),
|
|
593
|
+
...(typeof p.body === "string" ? { body: p.body } : {}),
|
|
594
|
+
},
|
|
595
|
+
...(typeof parsed.reason === "string" ? { reason: parsed.reason } : {}),
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
return out;
|
|
599
|
+
}
|
|
600
|
+
/** 写清单(原子写:tmp + rename)。 */
|
|
601
|
+
function writeContactEntries(agentDir, rows) {
|
|
602
|
+
const file = contactLedgerPath(agentDir);
|
|
603
|
+
try {
|
|
604
|
+
ensureDir(path.dirname(file));
|
|
605
|
+
const body = rows.map((r) => JSON.stringify(r)).join("\n");
|
|
606
|
+
writeAtomicFile(file, body === "" ? "" : `${body}\n`, { durable: true });
|
|
607
|
+
chmodQuiet(file, PROACTIVE_FILE_MODE);
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 追加一条联系记录(**按 eventId 去重**:同一事件只落一条,通道取并集)。
|
|
616
|
+
*
|
|
617
|
+
* @returns `true` = 新增了一条;`false` = 同一事件已存在(已合并通道,未重复落盘)。
|
|
618
|
+
*/
|
|
619
|
+
export function appendContactEntry(agentDir, entry) {
|
|
620
|
+
const rows = readContactEntries(agentDir);
|
|
621
|
+
const idx = rows.findIndex((r) => r.eventId === entry.eventId);
|
|
622
|
+
if (idx >= 0) {
|
|
623
|
+
const prev = rows[idx];
|
|
624
|
+
const channels = [...new Set([...prev.channels, ...entry.channels])];
|
|
625
|
+
const outcome = channels.includes("session") && channels.includes("device")
|
|
626
|
+
? "both"
|
|
627
|
+
: channels.includes("session")
|
|
628
|
+
? "session"
|
|
629
|
+
: channels.includes("device")
|
|
630
|
+
? "device"
|
|
631
|
+
: entry.outcome;
|
|
632
|
+
if (channels.length !== prev.channels.length || outcome !== prev.outcome) {
|
|
633
|
+
rows[idx] = { ...prev, channels, outcome, ts: Math.max(prev.ts, entry.ts) };
|
|
634
|
+
writeContactEntries(agentDir, rows);
|
|
635
|
+
}
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
rows.push(entry);
|
|
639
|
+
return writeContactEntries(agentDir, rows);
|
|
640
|
+
}
|
|
641
|
+
/** 清单 → 人话(用户只面对「它联系了我什么」;机器细节收在括号里)。 */
|
|
642
|
+
export function formatContactList(rows) {
|
|
643
|
+
if (rows.length === 0)
|
|
644
|
+
return "(还没有联系过)";
|
|
645
|
+
const lines = [`它联系了我什么(共 ${rows.length} 条):`];
|
|
646
|
+
for (const r of rows) {
|
|
647
|
+
const when = new Date(r.ts).toLocaleString();
|
|
648
|
+
const how = r.channels.length > 0 ? r.channels.join("+") : "无";
|
|
649
|
+
lines.push(`· ${when} · ${how}${r.targetAgentId ? ` · ${r.targetAgentId}` : ""}`);
|
|
650
|
+
lines.push(` 为什么找你:${r.payload.why}`);
|
|
651
|
+
lines.push(` 需要你做什么:${r.payload.ask}`);
|
|
652
|
+
lines.push(` 不做的后果:${r.payload.consequence}`);
|
|
653
|
+
lines.push(` 怎么关掉我:${r.payload.howToDisable}`);
|
|
654
|
+
if (r.reason !== undefined && r.reason !== "")
|
|
655
|
+
lines.push(` (${r.reason})`);
|
|
656
|
+
}
|
|
657
|
+
return lines.join("\n");
|
|
658
|
+
}
|
|
659
|
+
/** 跳过审计路径:`<agentDir>/proactive/daemon/contacts-skipped.jsonl`。 */
|
|
660
|
+
export function contactAuditPath(agentDir) {
|
|
661
|
+
return path.join(daemonDir(agentDir), "contacts-skipped.jsonl");
|
|
662
|
+
}
|
|
663
|
+
/** 读跳过审计(坏行只丢那一行)。 */
|
|
664
|
+
export function readContactAudit(agentDir) {
|
|
665
|
+
const file = contactAuditPath(agentDir);
|
|
666
|
+
let raw;
|
|
667
|
+
try {
|
|
668
|
+
raw = fs.readFileSync(file, "utf8");
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
const out = [];
|
|
674
|
+
for (const line of raw.split("\n")) {
|
|
675
|
+
const text = line.trim();
|
|
676
|
+
if (text === "")
|
|
677
|
+
continue;
|
|
678
|
+
try {
|
|
679
|
+
const parsed = JSON.parse(text);
|
|
680
|
+
if (typeof parsed.ts !== "number" || typeof parsed.reason !== "string")
|
|
681
|
+
continue;
|
|
682
|
+
out.push({
|
|
683
|
+
ts: parsed.ts,
|
|
684
|
+
reason: parsed.reason,
|
|
685
|
+
...(typeof parsed.targetAgentId === "string" ? { targetAgentId: parsed.targetAgentId } : {}),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return out;
|
|
693
|
+
}
|
|
694
|
+
/** 追加一条跳过审计(**上限 200 条**,超出丢最旧 —— 免得无限增长)。 */
|
|
695
|
+
export function appendContactAudit(agentDir, entry) {
|
|
696
|
+
const rows = readContactAudit(agentDir);
|
|
697
|
+
rows.push(entry);
|
|
698
|
+
const trimmed = rows.slice(Math.max(0, rows.length - 200));
|
|
699
|
+
const file = contactAuditPath(agentDir);
|
|
700
|
+
try {
|
|
701
|
+
ensureDir(path.dirname(file));
|
|
702
|
+
writeAtomicFile(file, `${trimmed.map((r) => JSON.stringify(r)).join("\n")}\n`, { durable: true });
|
|
703
|
+
chmodQuiet(file, PROACTIVE_FILE_MODE);
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { dayKey } from "./daemon-config.js";
|
|
2
|
+
/** 心跳过期倍数(与 `src/registry.ts` 的口径一致:3 × 心跳周期)。 */
|
|
3
|
+
const OFFLINE_HEARTBEAT_FACTOR = 3;
|
|
4
|
+
/** 条目是否在线(心跳新鲜且未优雅退出)。 */
|
|
5
|
+
function isOnline(entry, now) {
|
|
6
|
+
if (entry.status === "offline")
|
|
7
|
+
return false;
|
|
8
|
+
if (now < entry.lastSeen)
|
|
9
|
+
return false;
|
|
10
|
+
return now - entry.lastSeen <= entry.heartbeatMs * OFFLINE_HEARTBEAT_FACTOR;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 判定「联系哪个会话」。
|
|
14
|
+
*
|
|
15
|
+
* 口径(**可判定**):
|
|
16
|
+
* - 候选 = 注册表里**带会话 id**(= 已登记会话)的条目,排除常驻进程自己;
|
|
17
|
+
* - `target` 非 `"auto"` → 必须**逐字命中**候选,否则**不联系**(不许近似匹配);
|
|
18
|
+
* - `target === "auto"` → 优先在线,其次最近活跃(`lastSeen` 降序),再按 agentId 升序兜底(**确定性**)。
|
|
19
|
+
*/
|
|
20
|
+
export function selectContactTarget(entries, opts) {
|
|
21
|
+
const now = opts.now ?? Date.now();
|
|
22
|
+
const candidates = entries.filter((e) => e.sessionId !== undefined && e.agentId !== opts.selfAgentId);
|
|
23
|
+
if (opts.target !== "auto") {
|
|
24
|
+
const hit = candidates.find((e) => e.agentId === opts.target);
|
|
25
|
+
if (hit === undefined) {
|
|
26
|
+
return {
|
|
27
|
+
target: null,
|
|
28
|
+
reason: `指定的目标未登记(不是已登记会话):${opts.target}`,
|
|
29
|
+
candidates: candidates.length,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { target: hit, reason: `用户指定的目标会话:${hit.agentId}`, candidates: candidates.length };
|
|
33
|
+
}
|
|
34
|
+
if (candidates.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
target: null,
|
|
37
|
+
reason: "没有已登记会话(注册表里没有带会话 id 的条目)→ 不联系",
|
|
38
|
+
candidates: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const sorted = [...candidates].sort((a, b) => {
|
|
42
|
+
const onlineA = isOnline(a, now) ? 1 : 0;
|
|
43
|
+
const onlineB = isOnline(b, now) ? 1 : 0;
|
|
44
|
+
if (onlineA !== onlineB)
|
|
45
|
+
return onlineB - onlineA;
|
|
46
|
+
if (a.lastSeen !== b.lastSeen)
|
|
47
|
+
return b.lastSeen - a.lastSeen;
|
|
48
|
+
return a.agentId < b.agentId ? -1 : a.agentId > b.agentId ? 1 : 0;
|
|
49
|
+
});
|
|
50
|
+
const picked = sorted[0];
|
|
51
|
+
return {
|
|
52
|
+
target: picked,
|
|
53
|
+
reason: `最近活跃的已登记会话:${picked.agentId}`,
|
|
54
|
+
candidates: candidates.length,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** 这条记录是否算「真的联系过」(跳过 / 失败的不算)。 */
|
|
58
|
+
function isRealContact(entry) {
|
|
59
|
+
return entry.outcome === "session" || entry.outcome === "device" || entry.outcome === "both";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 判定「现在该不该联系」。
|
|
63
|
+
*
|
|
64
|
+
* 顺序(刻意的):开关 → 目标 → 频次 → 每日上限。
|
|
65
|
+
* 关掉时**最先**短路(用户说「随时可关」→ 关掉就彻底安静,不留任何动作)。
|
|
66
|
+
*/
|
|
67
|
+
export function decideContact(input) {
|
|
68
|
+
const { config, entries, recent, now } = input;
|
|
69
|
+
if (!config.enabled) {
|
|
70
|
+
return { action: "skip", reason: "主动联系已关闭(开关在 contact.enabled)" };
|
|
71
|
+
}
|
|
72
|
+
const selection = selectContactTarget(entries, {
|
|
73
|
+
target: config.target,
|
|
74
|
+
now,
|
|
75
|
+
...(input.selfAgentId !== undefined ? { selfAgentId: input.selfAgentId } : {}),
|
|
76
|
+
});
|
|
77
|
+
if (selection.target === null) {
|
|
78
|
+
return { action: "skip", reason: selection.reason };
|
|
79
|
+
}
|
|
80
|
+
const real = recent.filter(isRealContact);
|
|
81
|
+
const last = real.reduce((max, e) => (e.ts > max ? e.ts : max), 0);
|
|
82
|
+
if (last > 0 && config.minIntervalMs > 0 && now - last < config.minIntervalMs) {
|
|
83
|
+
return {
|
|
84
|
+
action: "skip",
|
|
85
|
+
reason: `频次未到(距上次联系 ${Math.round((now - last) / 1000)}s < 下限 ${Math.round(config.minIntervalMs / 1000)}s)`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const today = dayKey(now);
|
|
89
|
+
const todayCount = real.filter((e) => dayKey(e.ts) === today).length;
|
|
90
|
+
if (config.maxPerDay > 0 && todayCount >= config.maxPerDay) {
|
|
91
|
+
return {
|
|
92
|
+
action: "skip",
|
|
93
|
+
reason: `今日已达每日上限(${todayCount}/${config.maxPerDay} 次)`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const target = selection.target;
|
|
97
|
+
return {
|
|
98
|
+
action: "contact",
|
|
99
|
+
reason: selection.reason,
|
|
100
|
+
target,
|
|
101
|
+
eventId: `evt-${now.toString(36)}-${target.agentId}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 常驻**宿主**判定(双宿主:herdr 优先 → 自带 launchd/systemd 回落)。
|
|
3
|
+
*
|
|
4
|
+
* 形态选型 §2.5 的硬约束:
|
|
5
|
+
* - herdr 只是**保活容器**(助理在其中纯 headless:无 TUI、不抢 stdin);
|
|
6
|
+
* - herdr 探测不到 → **回落**自带 daemon(**不绑死单一宿主**);
|
|
7
|
+
* - 用户入口只有两个:自己正在用的 pi 会话 + 设备级通知 —— **不让用户进另一个终端**。
|
|
8
|
+
*
|
|
9
|
+
* 本模块**只判定**(纯函数 + 可注入执行器 → 测试零副作用)。
|
|
10
|
+
*/
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
/** 默认执行器(同步、3 秒超时、不继承 stdin)。 */
|
|
13
|
+
export const defaultHostExec = (cmd, args) => {
|
|
14
|
+
const res = spawnSync(cmd, args, { timeout: 3000, stdio: ["ignore", "pipe", "pipe"] });
|
|
15
|
+
return {
|
|
16
|
+
ok: res.status === 0 && !res.error,
|
|
17
|
+
code: res.status ?? 1,
|
|
18
|
+
stdout: res.stdout ? res.stdout.toString("utf8") : "",
|
|
19
|
+
stderr: res.stderr ? res.stderr.toString("utf8") : "",
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/** herdr 的服务端是否在跑(`herdr status server` → `status: running`)。 */
|
|
23
|
+
export function herdrServerRunning(exec = defaultHostExec) {
|
|
24
|
+
try {
|
|
25
|
+
const res = exec("herdr", ["status", "server"]);
|
|
26
|
+
return res.ok && /status:\s*running/i.test(res.stdout);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 判定宿主:
|
|
34
|
+
*
|
|
35
|
+
* - herdr 在跑 → `herdr`(保活容器 + `herdr notification show` 通知通道);
|
|
36
|
+
* - 否则 → 平台对应的系统宿主(macOS = launchd / Linux = systemd --user),
|
|
37
|
+
* 原因里写明「回落」(**用户看得见降级发生了**)。
|
|
38
|
+
*/
|
|
39
|
+
export function detectHost(opts = {}) {
|
|
40
|
+
const platform = opts.platform ?? process.platform;
|
|
41
|
+
const exec = opts.exec ?? defaultHostExec;
|
|
42
|
+
let herdrOut = "";
|
|
43
|
+
try {
|
|
44
|
+
const res = exec("herdr", ["status", "server"]);
|
|
45
|
+
herdrOut = `${res.stdout}${res.stderr}`.trim();
|
|
46
|
+
if (res.ok && /status:\s*running/i.test(res.stdout)) {
|
|
47
|
+
return {
|
|
48
|
+
hostKind: "herdr",
|
|
49
|
+
available: true,
|
|
50
|
+
reason: "herdr 可用 → 常驻宿主 = herdr workspace/session(助理在其中纯 headless)",
|
|
51
|
+
detail: herdrOut,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// 探测失败 → 当作不可用(回落)
|
|
57
|
+
}
|
|
58
|
+
const hostKind = platform === "darwin" ? "launchd" : "systemd";
|
|
59
|
+
return {
|
|
60
|
+
hostKind,
|
|
61
|
+
available: true,
|
|
62
|
+
reason: `herdr 不可用 → 回落自带系统宿主(${hostKind})`,
|
|
63
|
+
detail: herdrOut === "" ? undefined : herdrOut,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------- CLI(给安装脚本用)
|
|
67
|
+
/** 判断是否为主模块直接运行。 */
|
|
68
|
+
function isMainModule() {
|
|
69
|
+
const entry = process.argv[1];
|
|
70
|
+
if (!entry)
|
|
71
|
+
return false;
|
|
72
|
+
// 产物态入口是 build/src/daemon-host.js(编译后后缀变了)→ 只认 .ts 时产物态静默不执行。
|
|
73
|
+
return entry.endsWith("daemon-host.ts") || entry.endsWith("daemon-host.js");
|
|
74
|
+
}
|
|
75
|
+
if (isMainModule()) {
|
|
76
|
+
const detection = detectHost();
|
|
77
|
+
// `--kind`:只打印宿主种类(给安装脚本用;免得它在 bash 里解析 JSON)。
|
|
78
|
+
if (process.argv.includes("--kind")) {
|
|
79
|
+
process.stdout.write(`${detection.hostKind}\n`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
process.stdout.write(`${JSON.stringify(detection)}\n`);
|
|
83
|
+
}
|
|
84
|
+
}
|