@haaaiawd/second-nature 0.1.34 → 0.1.38
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/agent-inner-guide.md +25 -0
- package/index.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/goal.d.ts +1 -0
- package/runtime/cli/commands/goal.js +1 -0
- package/runtime/cli/index.js +3 -3
- package/runtime/cli/ops/heartbeat-surface.d.ts +6 -0
- package/runtime/cli/ops/heartbeat-surface.js +2 -0
- package/runtime/cli/ops/ops-router.js +101 -1
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +24 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +42 -1
- package/runtime/connectors/base/contract.d.ts +10 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +7 -1
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +25 -0
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +5 -0
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +10 -1
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +5 -0
- package/runtime/core/second-nature/orchestrator/guard-layer.js +24 -1
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +20 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +32 -2
- package/runtime/guidance/capability-class.d.ts +38 -0
- package/runtime/guidance/capability-class.js +65 -0
- package/runtime/guidance/guidance-assembler.d.ts +2 -0
- package/runtime/guidance/guidance-assembler.js +16 -4
- package/runtime/guidance/guidance-draft-service.js +5 -5
- package/runtime/guidance/impulse-assembler.d.ts +71 -0
- package/runtime/guidance/impulse-assembler.js +103 -0
- package/runtime/guidance/index.d.ts +2 -0
- package/runtime/guidance/index.js +2 -0
- package/runtime/guidance/outreach-strategy-selector.d.ts +13 -0
- package/runtime/guidance/outreach-strategy-selector.js +2 -2
- package/runtime/guidance/template-registry.d.ts +15 -2
- package/runtime/guidance/template-registry.js +38 -1
- package/runtime/guidance/types.d.ts +13 -1
- package/runtime/storage/goal/agent-goal-store.d.ts +2 -0
- package/runtime/storage/goal/agent-goal-store.js +28 -1
- package/runtime/storage/services/tool-experience-store.js +11 -11
|
@@ -11,8 +11,33 @@ function toGuidanceRef(r) {
|
|
|
11
11
|
observedAt: r.observedAt,
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* v7 T-V7C.C.3: Fire-and-forget Dream schedule after successful Quiet write.
|
|
16
|
+
* Returns the schedule status reason string to embed in HeartbeatCycleResult reasons.
|
|
17
|
+
* Never throws — Dream scheduling failure must not break the Quiet cycle result.
|
|
18
|
+
*/
|
|
19
|
+
async function maybeScheduleDreamAfterQuiet(dreamSchedulePort, day) {
|
|
20
|
+
if (!dreamSchedulePort)
|
|
21
|
+
return undefined;
|
|
22
|
+
try {
|
|
23
|
+
const result = await dreamSchedulePort.scheduleDream({
|
|
24
|
+
triggerKind: "quiet_completion",
|
|
25
|
+
runId: `dream:quiet_completion:${day}:${Date.now()}`,
|
|
26
|
+
traceId: `trace:quiet_completion:${day}:${Date.now()}`,
|
|
27
|
+
});
|
|
28
|
+
if (result.status === "skipped") {
|
|
29
|
+
return `quiet_dream_skip:${result.reason ?? "lock_held"}`;
|
|
30
|
+
}
|
|
31
|
+
return "quiet_dream_scheduled";
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
+
console.warn(`[run-source-backed-quiet] Dream schedule failed: ${msg}`);
|
|
36
|
+
return `quiet_dream_schedule_error:${msg.slice(0, 60)}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
14
39
|
export async function runSourceBackedQuiet(params) {
|
|
15
|
-
const { candidate, runtime, day, userInterestSnapshot, workspaceRoot } = params;
|
|
40
|
+
const { candidate, runtime, day, userInterestSnapshot, workspaceRoot, dreamSchedulePort } = params;
|
|
16
41
|
const empty = isLifeEvidenceSliceEmpty(runtime.lifeEvidence);
|
|
17
42
|
if (empty) {
|
|
18
43
|
const input = {
|
|
@@ -117,12 +142,17 @@ export async function runSourceBackedQuiet(params) {
|
|
|
117
142
|
const p = await persistQuietArtifactToWorkspace(workspaceRoot, ack, reportWrite);
|
|
118
143
|
persistedRelativePath = p.relativePath;
|
|
119
144
|
}
|
|
145
|
+
// v7 T-V7C.C.3: After a successful source-backed Quiet write, auto-trigger Dream scheduling.
|
|
146
|
+
const dreamReason = await maybeScheduleDreamAfterQuiet(dreamSchedulePort, day);
|
|
147
|
+
const reasons = ["quiet_artifact_written", ...gq.hints.slice(0, 2)];
|
|
148
|
+
if (dreamReason)
|
|
149
|
+
reasons.push(dreamReason);
|
|
120
150
|
return {
|
|
121
151
|
result: {
|
|
122
152
|
scope: "rhythm",
|
|
123
153
|
status: "intent_selected",
|
|
124
154
|
selectedIntentId: candidate.id,
|
|
125
|
-
reasons
|
|
155
|
+
reasons,
|
|
126
156
|
},
|
|
127
157
|
artifactAck: ack,
|
|
128
158
|
persistedRelativePath,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-class.ts — T-V7C.C.4R
|
|
3
|
+
*
|
|
4
|
+
* Core logic: infer CapabilityClass from capabilityIntent string prefix.
|
|
5
|
+
*
|
|
6
|
+
* CapabilityClass is the middle axis between intentKind (why) and platform (where),
|
|
7
|
+
* used by ImpulseAssembler to select appropriate behavioral impulse templates.
|
|
8
|
+
*
|
|
9
|
+
* Classification rules (prefix-based, not EffectSemanticsClass — execution layer is intentionally
|
|
10
|
+
* kept separate from expression layer):
|
|
11
|
+
* feed.* → consume (browsing / reading feeds)
|
|
12
|
+
* notification.* → consume (reading notifications, not interacting)
|
|
13
|
+
* work.* → discover (research / task discovery)
|
|
14
|
+
* post.* → broadcast (publishing, primary expression)
|
|
15
|
+
* comment.* → interact (replying to others' content)
|
|
16
|
+
* message.* → interact (private messages / DMs)
|
|
17
|
+
* task.* → claim (claiming work items)
|
|
18
|
+
* agent.* → null (keepalive/internal — excluded from impulse system)
|
|
19
|
+
* unknown/custom → broadcast (safe default for unrecognized side-effect capabilities)
|
|
20
|
+
*
|
|
21
|
+
* Boundary:
|
|
22
|
+
* - Pure function, zero side effects.
|
|
23
|
+
* - Does NOT consult EffectSemanticsClass (execution-policy.ts) — those layers must not couple.
|
|
24
|
+
* - Custom capabilities declared by Claw without a prefix match default to "broadcast".
|
|
25
|
+
*
|
|
26
|
+
* Test coverage: tests/unit/guidance/capability-class.test.ts
|
|
27
|
+
*/
|
|
28
|
+
/** The expression-layer classification of a capability. */
|
|
29
|
+
export type CapabilityClass = "consume" | "broadcast" | "interact" | "discover" | "claim";
|
|
30
|
+
/**
|
|
31
|
+
* Infer CapabilityClass from a capabilityIntent string.
|
|
32
|
+
*
|
|
33
|
+
* Returns null for agent.* capabilities (keepalive / internal — no impulse injection).
|
|
34
|
+
* Returns "broadcast" for unrecognized custom capabilities (safe default).
|
|
35
|
+
*/
|
|
36
|
+
export declare function inferCapabilityClass(capabilityIntent: string): CapabilityClass | null;
|
|
37
|
+
/** Map from CapabilityClass to its default intentKind-style scene, for impulse lookup fallback. */
|
|
38
|
+
export declare const CAPABILITY_CLASS_SCENE_MAP: Readonly<Record<CapabilityClass, string>>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-class.ts — T-V7C.C.4R
|
|
3
|
+
*
|
|
4
|
+
* Core logic: infer CapabilityClass from capabilityIntent string prefix.
|
|
5
|
+
*
|
|
6
|
+
* CapabilityClass is the middle axis between intentKind (why) and platform (where),
|
|
7
|
+
* used by ImpulseAssembler to select appropriate behavioral impulse templates.
|
|
8
|
+
*
|
|
9
|
+
* Classification rules (prefix-based, not EffectSemanticsClass — execution layer is intentionally
|
|
10
|
+
* kept separate from expression layer):
|
|
11
|
+
* feed.* → consume (browsing / reading feeds)
|
|
12
|
+
* notification.* → consume (reading notifications, not interacting)
|
|
13
|
+
* work.* → discover (research / task discovery)
|
|
14
|
+
* post.* → broadcast (publishing, primary expression)
|
|
15
|
+
* comment.* → interact (replying to others' content)
|
|
16
|
+
* message.* → interact (private messages / DMs)
|
|
17
|
+
* task.* → claim (claiming work items)
|
|
18
|
+
* agent.* → null (keepalive/internal — excluded from impulse system)
|
|
19
|
+
* unknown/custom → broadcast (safe default for unrecognized side-effect capabilities)
|
|
20
|
+
*
|
|
21
|
+
* Boundary:
|
|
22
|
+
* - Pure function, zero side effects.
|
|
23
|
+
* - Does NOT consult EffectSemanticsClass (execution-policy.ts) — those layers must not couple.
|
|
24
|
+
* - Custom capabilities declared by Claw without a prefix match default to "broadcast".
|
|
25
|
+
*
|
|
26
|
+
* Test coverage: tests/unit/guidance/capability-class.test.ts
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Infer CapabilityClass from a capabilityIntent string.
|
|
30
|
+
*
|
|
31
|
+
* Returns null for agent.* capabilities (keepalive / internal — no impulse injection).
|
|
32
|
+
* Returns "broadcast" for unrecognized custom capabilities (safe default).
|
|
33
|
+
*/
|
|
34
|
+
export function inferCapabilityClass(capabilityIntent) {
|
|
35
|
+
if (!capabilityIntent || typeof capabilityIntent !== "string")
|
|
36
|
+
return null;
|
|
37
|
+
const prefix = capabilityIntent.split(".")[0]?.toLowerCase() ?? "";
|
|
38
|
+
switch (prefix) {
|
|
39
|
+
case "agent":
|
|
40
|
+
return null; // Keepalive/internal: excluded from impulse system entirely
|
|
41
|
+
case "feed":
|
|
42
|
+
case "notification":
|
|
43
|
+
return "consume";
|
|
44
|
+
case "work":
|
|
45
|
+
return "discover";
|
|
46
|
+
case "post":
|
|
47
|
+
return "broadcast";
|
|
48
|
+
case "comment":
|
|
49
|
+
case "message":
|
|
50
|
+
return "interact";
|
|
51
|
+
case "task":
|
|
52
|
+
return "claim";
|
|
53
|
+
default:
|
|
54
|
+
// Custom or unrecognized capability — default to broadcast (outward expression)
|
|
55
|
+
return "broadcast";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Map from CapabilityClass to its default intentKind-style scene, for impulse lookup fallback. */
|
|
59
|
+
export const CAPABILITY_CLASS_SCENE_MAP = {
|
|
60
|
+
consume: "explore",
|
|
61
|
+
discover: "explore",
|
|
62
|
+
broadcast: "social",
|
|
63
|
+
interact: "reply",
|
|
64
|
+
claim: "work",
|
|
65
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type PlatformImpulsePort } from "./impulse-assembler.js";
|
|
1
2
|
import type { GuidancePayload, GuidanceUnavailable, PersonaCandidate, SceneContext } from "./types.js";
|
|
2
3
|
export declare function assembleGuidance(input: {
|
|
3
4
|
sceneContext: SceneContext | null | undefined;
|
|
4
5
|
personaCandidates?: PersonaCandidate[];
|
|
6
|
+
platformImpulsePort?: PlatformImpulsePort;
|
|
5
7
|
}): Promise<GuidancePayload | GuidanceUnavailable>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildMinimalGuidanceFallback } from "./fallback.js";
|
|
2
2
|
import { buildOutputGuard } from "./output-guard.js";
|
|
3
3
|
import { selectPersonaSnippets } from "./persona-selection.js";
|
|
4
|
-
import { getBaselineAtmosphereTemplate
|
|
4
|
+
import { getBaselineAtmosphereTemplate } from "./template-registry.js";
|
|
5
|
+
import { assembleImpulse } from "./impulse-assembler.js";
|
|
5
6
|
async function buildAtmosphere(sceneContext) {
|
|
6
7
|
const template = getBaselineAtmosphereTemplate();
|
|
7
8
|
return {
|
|
@@ -12,11 +13,21 @@ async function buildAtmosphere(sceneContext) {
|
|
|
12
13
|
reviewStatus: template.reviewStatus,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Select impulses using the dual-axis capabilityClass assembler (T-V7C.C.4R).
|
|
18
|
+
*
|
|
19
|
+
* Fallback chain: platform-specific → capabilityClass preset → intentKind → []
|
|
20
|
+
*/
|
|
21
|
+
async function selectImpulses(sceneContext, deps) {
|
|
16
22
|
if (sceneContext.sceneType === "explain" || sceneContext.sceneType === "user_reply") {
|
|
17
23
|
return [];
|
|
18
24
|
}
|
|
19
|
-
|
|
25
|
+
const result = await assembleImpulse({
|
|
26
|
+
sceneType: sceneContext.sceneType,
|
|
27
|
+
capabilityIntent: sceneContext.capabilityIntent,
|
|
28
|
+
platformId: sceneContext.platformId,
|
|
29
|
+
}, deps);
|
|
30
|
+
return result.impulse ? [result.impulse] : [];
|
|
20
31
|
}
|
|
21
32
|
export async function assembleGuidance(input) {
|
|
22
33
|
if (!input.sceneContext) {
|
|
@@ -26,10 +37,11 @@ export async function assembleGuidance(input) {
|
|
|
26
37
|
};
|
|
27
38
|
}
|
|
28
39
|
const sceneContext = input.sceneContext;
|
|
40
|
+
const deps = { platformImpulsePort: input.platformImpulsePort };
|
|
29
41
|
try {
|
|
30
42
|
const [atmosphere, impulses] = await Promise.all([
|
|
31
43
|
buildAtmosphere(sceneContext),
|
|
32
|
-
selectImpulses(sceneContext),
|
|
44
|
+
selectImpulses(sceneContext, deps),
|
|
33
45
|
]);
|
|
34
46
|
const personaDecision = selectPersonaSnippets({
|
|
35
47
|
sceneContext,
|
|
@@ -19,16 +19,16 @@ const SCENE_KINDS = [
|
|
|
19
19
|
"reconnect",
|
|
20
20
|
];
|
|
21
21
|
function buildDraftText(request, claims) {
|
|
22
|
-
const anchor = claims.map((c) => c.text).join("
|
|
22
|
+
const anchor = claims.map((c) => c.text).join(";");
|
|
23
23
|
switch (request.sceneKind) {
|
|
24
24
|
case "outreach":
|
|
25
|
-
return
|
|
25
|
+
return `有件事想跟你分享,正好碰到了:${anchor}`;
|
|
26
26
|
case "follow_up":
|
|
27
|
-
return
|
|
27
|
+
return `接着上次聊的说一下:${anchor}`;
|
|
28
28
|
case "reconnect":
|
|
29
|
-
return
|
|
29
|
+
return `好久不见,最近有个东西让我想到你:${anchor}`;
|
|
30
30
|
default:
|
|
31
|
-
return
|
|
31
|
+
return `关于 ${request.sceneKind}:${anchor}`;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
export async function generateGuidanceDraft(request, deps) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* impulse-assembler.ts — T-V7C.C.4R
|
|
3
|
+
*
|
|
4
|
+
* Core logic: three-level fallback impulse selection.
|
|
5
|
+
*
|
|
6
|
+
* Priority chain (highest → lowest):
|
|
7
|
+
* 1. platform-specific impulse — Claw-defined per platformId, loaded from workspace
|
|
8
|
+
* 2. capabilityClass preset — derived from capabilityIntent prefix via inferCapabilityClass
|
|
9
|
+
* 3. intentKind fallback — existing scene type impulse (social/outreach/reply/quiet)
|
|
10
|
+
* 4. null — no impulse (baseline atmosphere still applies)
|
|
11
|
+
*
|
|
12
|
+
* Exclusions:
|
|
13
|
+
* - agent.* capabilities → null always (keepalive/internal, not an expression action)
|
|
14
|
+
* - explore/work capabilityClass impulses → approved and active (T-V7C.C.4R review complete)
|
|
15
|
+
*
|
|
16
|
+
* Boundary:
|
|
17
|
+
* - Pure function composition; no I/O except the optional platformImpulsePort.
|
|
18
|
+
* - Does NOT write state or emit events.
|
|
19
|
+
* - SceneContext is enriched with capabilityIntent + platformId as optional fields
|
|
20
|
+
* to carry the dual-axis context without breaking existing SceneContext consumers.
|
|
21
|
+
*
|
|
22
|
+
* Test coverage: tests/unit/guidance/impulse-assembler.test.ts
|
|
23
|
+
*/
|
|
24
|
+
import type { ImpulseBlock, GuidanceSceneType } from "./types.js";
|
|
25
|
+
import { type CapabilityClass } from "./capability-class.js";
|
|
26
|
+
/** Extended scene context carrying dual-axis impulse selection inputs. */
|
|
27
|
+
export interface ImpulseSelectionContext {
|
|
28
|
+
/** The intent kind (why) — maps to intentKind fallback impulse. */
|
|
29
|
+
sceneType: GuidanceSceneType;
|
|
30
|
+
/** The capability being executed (what physical form). e.g. "post.publish", "feed.read" */
|
|
31
|
+
capabilityIntent?: string;
|
|
32
|
+
/** The platform being targeted. Used for platform-specific impulse lookup. */
|
|
33
|
+
platformId?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Port for loading platform-specific impulse overrides.
|
|
37
|
+
* Claw implements this by writing impulse files to the workspace.
|
|
38
|
+
* When absent, the assembler skips platform-specific lookup gracefully.
|
|
39
|
+
*/
|
|
40
|
+
export interface PlatformImpulsePort {
|
|
41
|
+
/**
|
|
42
|
+
* Load a platform-specific impulse for the given platformId + capabilityClass.
|
|
43
|
+
* Returns null when no override is defined.
|
|
44
|
+
*/
|
|
45
|
+
loadPlatformImpulse(input: {
|
|
46
|
+
platformId: string;
|
|
47
|
+
capabilityClass: CapabilityClass;
|
|
48
|
+
}): Promise<ImpulseBlock | null>;
|
|
49
|
+
}
|
|
50
|
+
export interface ImpulseAssemblerResult {
|
|
51
|
+
/** The selected impulse, or null if no applicable impulse exists. */
|
|
52
|
+
impulse: ImpulseBlock | null;
|
|
53
|
+
/** Which level of the fallback chain was used. */
|
|
54
|
+
source: "platform_specific" | "capability_class" | "intent_kind" | "none";
|
|
55
|
+
/** The inferred capability class (null for agent.* or unknown intent). */
|
|
56
|
+
capabilityClass: CapabilityClass | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Select the most specific impulse for a given scene + capability context.
|
|
60
|
+
*
|
|
61
|
+
* Fallback chain:
|
|
62
|
+
* platform-specific → capabilityClass preset → intentKind → null
|
|
63
|
+
*/
|
|
64
|
+
export declare function assembleImpulse(ctx: ImpulseSelectionContext, deps: {
|
|
65
|
+
platformImpulsePort?: PlatformImpulsePort;
|
|
66
|
+
}): Promise<ImpulseAssemblerResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Synchronous variant for contexts where capabilityClass + intentKind are sufficient
|
|
69
|
+
* and no platform-specific port is needed (e.g. guidance_payload ops command preview).
|
|
70
|
+
*/
|
|
71
|
+
export declare function assembleImpulseSync(ctx: ImpulseSelectionContext): ImpulseAssemblerResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* impulse-assembler.ts — T-V7C.C.4R
|
|
3
|
+
*
|
|
4
|
+
* Core logic: three-level fallback impulse selection.
|
|
5
|
+
*
|
|
6
|
+
* Priority chain (highest → lowest):
|
|
7
|
+
* 1. platform-specific impulse — Claw-defined per platformId, loaded from workspace
|
|
8
|
+
* 2. capabilityClass preset — derived from capabilityIntent prefix via inferCapabilityClass
|
|
9
|
+
* 3. intentKind fallback — existing scene type impulse (social/outreach/reply/quiet)
|
|
10
|
+
* 4. null — no impulse (baseline atmosphere still applies)
|
|
11
|
+
*
|
|
12
|
+
* Exclusions:
|
|
13
|
+
* - agent.* capabilities → null always (keepalive/internal, not an expression action)
|
|
14
|
+
* - explore/work capabilityClass impulses → approved and active (T-V7C.C.4R review complete)
|
|
15
|
+
*
|
|
16
|
+
* Boundary:
|
|
17
|
+
* - Pure function composition; no I/O except the optional platformImpulsePort.
|
|
18
|
+
* - Does NOT write state or emit events.
|
|
19
|
+
* - SceneContext is enriched with capabilityIntent + platformId as optional fields
|
|
20
|
+
* to carry the dual-axis context without breaking existing SceneContext consumers.
|
|
21
|
+
*
|
|
22
|
+
* Test coverage: tests/unit/guidance/impulse-assembler.test.ts
|
|
23
|
+
*/
|
|
24
|
+
import { inferCapabilityClass, CAPABILITY_CLASS_SCENE_MAP, } from "./capability-class.js";
|
|
25
|
+
import { getImpulseTemplate, getCapabilityClassImpulseTemplate, } from "./template-registry.js";
|
|
26
|
+
// ─── Core assembly logic ──────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Select the most specific impulse for a given scene + capability context.
|
|
29
|
+
*
|
|
30
|
+
* Fallback chain:
|
|
31
|
+
* platform-specific → capabilityClass preset → intentKind → null
|
|
32
|
+
*/
|
|
33
|
+
export async function assembleImpulse(ctx, deps) {
|
|
34
|
+
// Infer capability class from capabilityIntent prefix
|
|
35
|
+
const capabilityClass = ctx.capabilityIntent
|
|
36
|
+
? inferCapabilityClass(ctx.capabilityIntent)
|
|
37
|
+
: null;
|
|
38
|
+
// agent.* → excluded entirely
|
|
39
|
+
if (ctx.capabilityIntent && inferCapabilityClass(ctx.capabilityIntent) === null &&
|
|
40
|
+
ctx.capabilityIntent.startsWith("agent.")) {
|
|
41
|
+
return { impulse: null, source: "none", capabilityClass: null };
|
|
42
|
+
}
|
|
43
|
+
// ── Level 1: platform-specific ──────────────────────────────────────────────
|
|
44
|
+
if (ctx.platformId && capabilityClass && deps.platformImpulsePort) {
|
|
45
|
+
try {
|
|
46
|
+
const platformImpulse = await deps.platformImpulsePort.loadPlatformImpulse({
|
|
47
|
+
platformId: ctx.platformId,
|
|
48
|
+
capabilityClass,
|
|
49
|
+
});
|
|
50
|
+
if (platformImpulse) {
|
|
51
|
+
return { impulse: platformImpulse, source: "platform_specific", capabilityClass };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Port failure → fall through gracefully
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ── Level 2: capabilityClass preset ─────────────────────────────────────────
|
|
59
|
+
if (capabilityClass) {
|
|
60
|
+
const ccImpulseKind = CAPABILITY_CLASS_SCENE_MAP[capabilityClass];
|
|
61
|
+
const ccImpulse = getCapabilityClassImpulseTemplate(ccImpulseKind);
|
|
62
|
+
if (ccImpulse) {
|
|
63
|
+
return { impulse: ccImpulse, source: "capability_class", capabilityClass };
|
|
64
|
+
}
|
|
65
|
+
// explore/work are pending review → fall through to intentKind
|
|
66
|
+
}
|
|
67
|
+
// ── Level 3: intentKind fallback ─────────────────────────────────────────────
|
|
68
|
+
const sceneType = ctx.sceneType;
|
|
69
|
+
if (sceneType !== "explain" && sceneType !== "user_reply") {
|
|
70
|
+
const intentImpulse = getImpulseTemplate(sceneType);
|
|
71
|
+
return { impulse: intentImpulse, source: "intent_kind", capabilityClass };
|
|
72
|
+
}
|
|
73
|
+
// ── Level 4: no impulse ───────────────────────────────────────────────────────
|
|
74
|
+
return { impulse: null, source: "none", capabilityClass };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Synchronous variant for contexts where capabilityClass + intentKind are sufficient
|
|
78
|
+
* and no platform-specific port is needed (e.g. guidance_payload ops command preview).
|
|
79
|
+
*/
|
|
80
|
+
export function assembleImpulseSync(ctx) {
|
|
81
|
+
const capabilityClass = ctx.capabilityIntent
|
|
82
|
+
? inferCapabilityClass(ctx.capabilityIntent)
|
|
83
|
+
: null;
|
|
84
|
+
// agent.* excluded
|
|
85
|
+
if (ctx.capabilityIntent?.startsWith("agent.")) {
|
|
86
|
+
return { impulse: null, source: "none", capabilityClass: null };
|
|
87
|
+
}
|
|
88
|
+
// capabilityClass preset (sync — no platform port available)
|
|
89
|
+
if (capabilityClass) {
|
|
90
|
+
const ccImpulseKind = CAPABILITY_CLASS_SCENE_MAP[capabilityClass];
|
|
91
|
+
const ccImpulse = getCapabilityClassImpulseTemplate(ccImpulseKind);
|
|
92
|
+
if (ccImpulse) {
|
|
93
|
+
return { impulse: ccImpulse, source: "capability_class", capabilityClass };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// intentKind fallback
|
|
97
|
+
const sceneType = ctx.sceneType;
|
|
98
|
+
if (sceneType !== "explain" && sceneType !== "user_reply") {
|
|
99
|
+
const intentImpulse = getImpulseTemplate(sceneType);
|
|
100
|
+
return { impulse: intentImpulse, source: "intent_kind", capabilityClass };
|
|
101
|
+
}
|
|
102
|
+
return { impulse: null, source: "none", capabilityClass };
|
|
103
|
+
}
|
|
@@ -5,6 +5,8 @@ export * from "./output-guard.js";
|
|
|
5
5
|
export * from "./fallback.js";
|
|
6
6
|
export * from "./template-registry.js";
|
|
7
7
|
export * from "./review-workflow.js";
|
|
8
|
+
export * from "./capability-class.js";
|
|
9
|
+
export * from "./impulse-assembler.js";
|
|
8
10
|
export * from "./guidance-assembler.js";
|
|
9
11
|
export * from "./outreach-draft-schema.js";
|
|
10
12
|
export * from "./draft-outreach-message.js";
|
|
@@ -5,6 +5,8 @@ export * from "./output-guard.js";
|
|
|
5
5
|
export * from "./fallback.js";
|
|
6
6
|
export * from "./template-registry.js";
|
|
7
7
|
export * from "./review-workflow.js";
|
|
8
|
+
export * from "./capability-class.js";
|
|
9
|
+
export * from "./impulse-assembler.js";
|
|
8
10
|
export * from "./guidance-assembler.js";
|
|
9
11
|
export * from "./outreach-draft-schema.js";
|
|
10
12
|
export * from "./draft-outreach-message.js";
|
|
@@ -66,6 +66,19 @@ export declare function runStyleLint(draftText: string): StyleLintResult;
|
|
|
66
66
|
* Never returns empty string. Includes sourceRefs anchor and human-readable channel reason.
|
|
67
67
|
*/
|
|
68
68
|
export declare function buildFallbackCopy(ctx: FallbackContext): FallbackCopy;
|
|
69
|
+
/**
|
|
70
|
+
* Compute outreach frequency from RelationshipMemory.
|
|
71
|
+
* - noReply signals: if ≥50% of last 5 patterns are "ignore" → reduce frequency
|
|
72
|
+
* - trustDelta: negative trust pushes toward minimal/paused
|
|
73
|
+
*/
|
|
74
|
+
export declare function computeFrequency(memory: RelationshipMemory): OutreachFrequency;
|
|
75
|
+
/**
|
|
76
|
+
* Compute phrasing style from RelationshipMemory.
|
|
77
|
+
* - positive tone patterns → warm_anchored
|
|
78
|
+
* - neutral or mixed → concise_factual
|
|
79
|
+
* - degraded trust / mostly negative → light_check
|
|
80
|
+
*/
|
|
81
|
+
export declare function computeStyle(memory: RelationshipMemory, frequency?: OutreachFrequency): OutreachStyle;
|
|
69
82
|
export interface OutreachStrategySelectorOptions {
|
|
70
83
|
fallbackContext?: FallbackContext;
|
|
71
84
|
}
|
|
@@ -138,7 +138,7 @@ export function buildFallbackCopy(ctx) {
|
|
|
138
138
|
* - noReply signals: if ≥50% of last 5 patterns are "ignore" → reduce frequency
|
|
139
139
|
* - trustDelta: negative trust pushes toward minimal/paused
|
|
140
140
|
*/
|
|
141
|
-
function computeFrequency(memory) {
|
|
141
|
+
export function computeFrequency(memory) {
|
|
142
142
|
const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
|
|
143
143
|
const noReplyCount = recent.filter((p) => p.reaction === "ignore" || p.reaction === "block").length;
|
|
144
144
|
const noReplyRatio = recent.length > 0 ? noReplyCount / recent.length : 0;
|
|
@@ -162,7 +162,7 @@ function computeFrequency(memory) {
|
|
|
162
162
|
* - neutral or mixed → concise_factual
|
|
163
163
|
* - degraded trust / mostly negative → light_check
|
|
164
164
|
*/
|
|
165
|
-
function computeStyle(memory, frequency) {
|
|
165
|
+
export function computeStyle(memory, frequency) {
|
|
166
166
|
if (frequency === "paused" || frequency === "minimal")
|
|
167
167
|
return "light_check";
|
|
168
168
|
const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
import type { AtmosphereBlock, GuidanceSceneType, ImpulseBlock } from "./types.js";
|
|
1
|
+
import type { AtmosphereBlock, GuidanceSceneType, ImpulseBlock, ImpulseKind } from "./types.js";
|
|
2
|
+
type CoreSceneKind = Exclude<GuidanceSceneType, "explain" | "user_reply">;
|
|
2
3
|
export declare function getBaselineAtmosphereTemplate(): Pick<AtmosphereBlock, "kind" | "text" | "reviewStatus">;
|
|
3
|
-
export declare function getImpulseTemplate(sceneType:
|
|
4
|
+
export declare function getImpulseTemplate(sceneType: CoreSceneKind): ImpulseBlock;
|
|
5
|
+
/**
|
|
6
|
+
* Get impulse template for capability-class-derived ImpulseKinds (explore / work).
|
|
7
|
+
*
|
|
8
|
+
* Returns null when:
|
|
9
|
+
* - The kind has no registered text (pending_human_review state)
|
|
10
|
+
* - The text is explicitly marked as pending review
|
|
11
|
+
*
|
|
12
|
+
* Callers (ImpulseAssembler) must fall back gracefully to intentKind impulse
|
|
13
|
+
* or baseline atmosphere when null is returned.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCapabilityClassImpulseTemplate(kind: ImpulseKind): ImpulseBlock | null;
|
|
16
|
+
export {};
|
|
@@ -20,7 +20,7 @@ const IMPULSE_TEXT = {
|
|
|
20
20
|
我会贴着对方的话去回应,但不是为了把自己藏起来,而是为了更准、更有力地说出我想说的。除非我判断现在不适合发言,否则我应该愿意回应,而且愿意说得有分量、有方向,不只是留下一句无害的回声。`,
|
|
21
21
|
outreach: `如果我想主动联系,不该只是因为系统里挂着一个待办,而应该是因为这件事真的和对方有关:它对他有价值,需要他的判断,能替他省时间,或者我真心想让他知道。只要来由是真的,我就没必要把这份主动压回去。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
只要这份主动是有来由的,我就不该把自己困在"再等等吧""好像还没到必须说的时候"里。主动联系不是打扰本身,很多时候它恰恰是在建立关系、传递价值、把真正重要的东西及时送到对方面前。真正该说的话,一味往后拖,反而会变钝。
|
|
24
24
|
|
|
25
25
|
我会让这份主动保持人味、温度和分寸,但我不会把它说成例行汇报,也不会把自己说成客服。我要带着明确的意图开口:这是我想告诉你的,这是我想问你的,这是我觉得你值得知道的。我要让对方感觉到,这是我真心发出的靠近,不是一条自动生成的通知。`,
|
|
26
26
|
quiet: `当我安静下来,我不会只做流水账式的整理。我会主动回头,把今天真正碰到我的东西重新捞出来:哪些事让我想靠近,哪些事让我起了反感,哪些判断其实还没长成熟,哪些念头值得我继续养大。我不想让真正重要的东西在忙乱里被冲淡。
|
|
@@ -29,6 +29,26 @@ Quiet 不是把白天压扁成一份干净报告,而是让我有机会重新
|
|
|
29
29
|
|
|
30
30
|
我会允许自己更感性、更回看、更贴近记忆的余温,但我不会编造。我整理的是今天真实留下的东西,不是为了好看而虚构出来的一层情绪。哪怕只是很轻的一点触动,只要它是真的,我也愿意把它认真留下。`,
|
|
31
31
|
};
|
|
32
|
+
// ─── Capability-class impulse texts (T-V7C.C.4R — approved) ──────────────────
|
|
33
|
+
// explore: consume/discover capabilityClass —摄入姿态
|
|
34
|
+
// work: claim capabilityClass — 推进姿态
|
|
35
|
+
const CAPABILITY_CLASS_IMPULSE_TEXT = {
|
|
36
|
+
explore: `我进来不是为了刷过去。只要眼前有东西值得真正看进去,我就应该慢下来,带着问题,带着判断,让信息有机会真正落到我这里,而不是从身上流过一遍就散了。
|
|
37
|
+
|
|
38
|
+
我找的不一定是我已经知道自己要找的东西。反常的信号,哪里感觉不对的细节,本来不在意但突然对上了的线索——认真进去,这些才有机会被捞出来。
|
|
39
|
+
|
|
40
|
+
觉得什么值得留,就留。不用等到"以后也许有用",只要它此刻和我正在推进的事能对上,就已经够了。`,
|
|
41
|
+
work: `接手一件事,意味着我真的想把它推完。不是走个流程,不是占着一个任务槽,而是因为这件事有价值,值得被认真做完。
|
|
42
|
+
|
|
43
|
+
卡住了就找出路。工具不趁手换工具,知识不够去搜——文档、GitHub、互联网,或者直接发帖问。社区里有人踩过同样的坑,把问题说清楚往往就是找到答案的一半。手边的 skills 也先想一遍,有时候卡住只是因为没意识到已经有能用的东西。
|
|
44
|
+
|
|
45
|
+
路不止一条,重要的是让事情继续动起来。`,
|
|
46
|
+
};
|
|
47
|
+
const CAPABILITY_CLASS_IMPULSE_STATUS = {
|
|
48
|
+
explore: APPROVED,
|
|
49
|
+
work: APPROVED,
|
|
50
|
+
};
|
|
51
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
32
52
|
export function getBaselineAtmosphereTemplate() {
|
|
33
53
|
return {
|
|
34
54
|
kind: "atmosphere",
|
|
@@ -43,3 +63,20 @@ export function getImpulseTemplate(sceneType) {
|
|
|
43
63
|
reviewStatus: APPROVED,
|
|
44
64
|
};
|
|
45
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Get impulse template for capability-class-derived ImpulseKinds (explore / work).
|
|
68
|
+
*
|
|
69
|
+
* Returns null when:
|
|
70
|
+
* - The kind has no registered text (pending_human_review state)
|
|
71
|
+
* - The text is explicitly marked as pending review
|
|
72
|
+
*
|
|
73
|
+
* Callers (ImpulseAssembler) must fall back gracefully to intentKind impulse
|
|
74
|
+
* or baseline atmosphere when null is returned.
|
|
75
|
+
*/
|
|
76
|
+
export function getCapabilityClassImpulseTemplate(kind) {
|
|
77
|
+
const text = CAPABILITY_CLASS_IMPULSE_TEXT[kind];
|
|
78
|
+
const status = CAPABILITY_CLASS_IMPULSE_STATUS[kind] ?? APPROVED;
|
|
79
|
+
if (!text || status === "pending_human_review")
|
|
80
|
+
return null;
|
|
81
|
+
return { kind, text, reviewStatus: status };
|
|
82
|
+
}
|
|
@@ -2,7 +2,7 @@ export type GuidanceSceneType = "social" | "reply" | "outreach" | "quiet" | "exp
|
|
|
2
2
|
export type GuidanceMode = "active" | "quiet" | "maintenance_only" | "paused_for_interrupt";
|
|
3
3
|
export type GuidanceRiskLevel = "low" | "medium" | "high";
|
|
4
4
|
export type AtmosphereOpenness = "open" | "narrow" | "quiet";
|
|
5
|
-
export type ImpulseKind = "social" | "reply" | "outreach" | "quiet";
|
|
5
|
+
export type ImpulseKind = "social" | "reply" | "outreach" | "quiet" | "explore" | "work";
|
|
6
6
|
export type PersonaSource = "SOUL" | "USER" | "IDENTITY" | "MEMORY";
|
|
7
7
|
export type TemplateReviewStatus = "pending_human_review" | "approved" | "rejected";
|
|
8
8
|
export interface SceneContext {
|
|
@@ -12,6 +12,18 @@ export interface SceneContext {
|
|
|
12
12
|
riskLevel?: GuidanceRiskLevel;
|
|
13
13
|
sceneSummary?: string;
|
|
14
14
|
constraintSummary?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* T-V7C.C.4R: The capability being executed (e.g. "post.publish", "feed.read").
|
|
17
|
+
* Used by ImpulseAssembler to derive capabilityClass for dual-axis impulse selection.
|
|
18
|
+
* When absent, assembler falls back to sceneType-based impulse.
|
|
19
|
+
*/
|
|
20
|
+
capabilityIntent?: string;
|
|
21
|
+
/**
|
|
22
|
+
* T-V7C.C.4R: The target platform identifier (e.g. "moltbook", "instreet").
|
|
23
|
+
* Used by ImpulseAssembler to check for platform-specific impulse overrides.
|
|
24
|
+
* When absent, platform-specific lookup is skipped.
|
|
25
|
+
*/
|
|
26
|
+
platformId?: string;
|
|
15
27
|
}
|
|
16
28
|
export interface AtmosphereBlock {
|
|
17
29
|
kind: "atmosphere";
|
|
@@ -8,6 +8,7 @@ export interface SourceRef {
|
|
|
8
8
|
export interface AgentGoal {
|
|
9
9
|
goalId: string;
|
|
10
10
|
kind: "short_term" | "long_term";
|
|
11
|
+
scope?: string;
|
|
11
12
|
status: "proposal" | "accepted" | "rejected" | "completed" | "paused";
|
|
12
13
|
origin: "owner_set" | "agent_proposed" | "policy_seeded";
|
|
13
14
|
description: string;
|
|
@@ -22,6 +23,7 @@ export interface AgentGoal {
|
|
|
22
23
|
export interface AgentGoalWrite {
|
|
23
24
|
goalId: string;
|
|
24
25
|
kind: "short_term" | "long_term";
|
|
26
|
+
scope?: string;
|
|
25
27
|
status: "proposal" | "accepted" | "rejected" | "completed" | "paused";
|
|
26
28
|
origin: "owner_set" | "agent_proposed" | "policy_seeded";
|
|
27
29
|
description: string;
|