@adam0white/human-framework 2.15.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -410,6 +410,7 @@ Knowledge lives in optional `Person.prospection` and survives snapshot/restore.
410
410
  `projectPersona(source, mapping)` produces `PersonaProjectionSnapshot` (`hf-persona-state/1`).
411
411
  `admitPersona(projection, targetOpening, { mappingVersion, sourceId, targetId })` returns a detached
412
412
  `{ person, report }`. `validatePersonaProjection(unknown)` validates portable data without filling defaults.
413
+ Portable v1 accepts the published 2.14.1 source-engine stamp and the current engine; it preserves that source provenance. Unknown source engines remain rejected.
413
414
  These functions never enter the actor loop and never mutate the source or the host opening.
414
415
 
415
416
  The host supplies a versioned map of skill ids, selected episode ids and every continuing obligation.
@@ -440,3 +441,38 @@ commitment history prunes it. It removes only records matched to an existing, cl
440
441
  recursively frozen, detached `MakeUpReceipt` objects. Persist those receipts in the source replay. Pending,
441
442
  broken, released, exempt and missing matches remain unresolved. The normal actor loop never calls retirement;
442
443
  existing games and saves keep their previous behavior until a host opts in.
444
+
445
+
446
+ ### Calendar passports (2.16.0, opt-in)
447
+
448
+ Use `projectCalendarPersona(source, { version, skills, episodes, calendar })` when declared obligations must
449
+ travel. `calendar` has version `hf-persona-calendar-map/1`, one exact clock offset, explicit injective maps
450
+ for commitment, chain, action, entity and norm references, and the source-backed norm catalog.
451
+ The result is `hf-persona-calendar/1`, wrapping the original approved Person projection and a separate
452
+ calendar. It preserves continuing commitments, every owed record, actual retained receipts, held norm
453
+ understandings and lapse state. A missing scheduled receipt remains unresolved and is disclosed; it never
454
+ becomes a paid duty. `isContinuingCommitment(agenda, commitment)` lets a host construct the required maps
455
+ using the owning agenda's existing recurrence semantics.
456
+
457
+ `validateCalendarPersona` strictly validates portable data. `admitCalendarPersona(payload, opening,
458
+ { mappingVersion, sourceId, targetId, targetNorms })` checks the offset and exact catalog semantics before
459
+ returning a detached Person. The receiving calendar must be empty; authored role goals may remain. After
460
+ admission, the host uses ordinary agenda APIs and its declared calendar. No duty is waived, no new ruling
461
+ is invented, and no obligation is converted to a score. Activities, projects and incompatible identity or
462
+ health remain unsupported. The original passport format and no-call continuation remain unchanged.
463
+
464
+ ### Project protection and narration options
465
+
466
+ Projects can opt into undo protection with `enableProjects(person, { protectProgress: true })`.
467
+ Declare `diminishes: [prospectId]` on an offer or its described successor, or let the person
468
+ learn the exact action context from a perceived drop after its own completed act.
469
+ `adoptProjectTask(person, prospectId, task)` reuses an active project and its net credit
470
+ for a host task; an absent match returns `undefined`.
471
+
472
+ Narration remains unchanged unless requested. Set `person.lexicon.variety`,
473
+ `nounActions` or `materialMeaning` for the corresponding host wording. Use
474
+ `promptingLine(reading, outcome, kind, { voice: 'third' })` for plain third-person lines.
475
+ `converse` adds per-call `loyalWarning`, `confidentMin` and `phrase` options;
476
+ `perceive` adds a third `{ reputationTraits }` argument. See the architecture's
477
+ [narration and social host options](../../docs/framework.md#narration-and-social-host-options-opt-in)
478
+ for cause-aware reviews and actor-addressed recall. These options change no default continuation.
@@ -56,6 +56,8 @@ export declare function createAgenda(spec: {
56
56
  export declare function promise(p: Person, c: Omit<Commitment, 'id' | 'status'>): Commitment;
57
57
  /** Release a pending commitment (the other party let it go). Released commitments do not recur. */
58
58
  export declare function release(p: Person, id: string): boolean;
59
+ /** Whether an instance still owns a pending duty or a recurrence that can produce its next window. Read-only. */
60
+ export declare const isContinuingCommitment: (state: AgendaState, c: Commitment) => boolean;
59
61
  /**
60
62
  * Whether an activity that would keep `c` is under way at `now`: begun no later than the window's end
61
63
  * (`spanMeetsWindow`), not yet ended, not sleep, and serving `c` (listed in `fulfills` or matching its action and
@@ -49,6 +49,7 @@
49
49
  import { DEFAULT_NORMS } from '../conscience/catalog.js';
50
50
  import { clamp01, dayOf, dpow, hasNumbers, isNum, isObj } from '../core/index.js';
51
51
  import { expectedEffect } from '../memory/index.js';
52
+ import { diminishesProject } from '../projects/index.js';
52
53
  import { MINUTES_PER_DAY } from '../types.js';
53
54
  export const AGENDA_DEFAULTS = {
54
55
  /** Minutes before a window opens during which a commitment already pulls (to get there). */
@@ -246,6 +247,13 @@ const sameShape = (a, b) => a.kind === b.kind &&
246
247
  const hasSuccessor = (state, c) => state.commitments.some((x) => x !== c && x.from > c.from && sameChain(x, c));
247
248
  /** Closed instances that carry their chain on: kept, broken, or released by an exemption (not by the other party). */
248
249
  const chainLink = (c) => c.status === 'kept' || c.status === 'broken' || (c.status === 'released' && c.exempt !== undefined);
250
+ /** Whether an instance still owns a pending duty or a recurrence that can produce its next window. Read-only. */
251
+ export const isContinuingCommitment = (state, c) => c.status === 'pending' ||
252
+ (c.recurEvery !== undefined &&
253
+ c.recurEvery > 0 &&
254
+ chainLink(c) &&
255
+ (c.recurUntil === undefined || c.from + c.recurEvery <= c.recurUntil) &&
256
+ !hasSuccessor(state, c));
249
257
  const needsSpawn = (state, c, now) => c.recurEvery !== undefined &&
250
258
  c.recurEvery > 0 &&
251
259
  chainLink(c) &&
@@ -520,6 +528,8 @@ function goalValue(g, aff, now) {
520
528
  }
521
529
  /** Opt-in completion-time and success estimate, shared by direct and received routes. @internal */
522
530
  export function temporalGoalValue(p, g, aff, now, completion) {
531
+ if (g.project && diminishesProject(p, aff, g.project.prospectId))
532
+ return 0;
523
533
  const base = goalValue(g, aff, now);
524
534
  return g.temporal
525
535
  ? base *
@@ -571,7 +581,7 @@ export function agendaTermsWithGoalEstimates(p, aff, now, goalOverrides) {
571
581
  terms.push({ source: `commitment:${c.id}`, value: d.commitmentScale * c.importance * pressure });
572
582
  }
573
583
  for (const g of p.agenda.goals) {
574
- if (g.status !== 'active')
584
+ if (g.status !== 'active' || (g.project && diminishesProject(p, aff, g.project.prospectId)))
575
585
  continue;
576
586
  const override = goalOverrides?.get(g.id);
577
587
  if (override !== undefined || g.temporal) {
@@ -55,6 +55,12 @@ export interface ConverseContext {
55
55
  traits?: Record<string, TraitEffect>;
56
56
  /** Carry belief origin and opaque lineage into told claims (off by default). */
57
57
  provenance?: boolean;
58
+ /** Relay beliefs this far from 0.5 (0..0.5); default 0.2. Invalid values use the default. */
59
+ confidentMin?: number;
60
+ /** Let a fond speaker warn the listener of an unfavourable character claim about them. */
61
+ loyalWarning?: boolean;
62
+ /** Presentation only. Receives a told claim or a withheld reason and its default summary; never changes selection. */
63
+ phrase?: (claim: Readonly<ToldClaim | WithheldClaim>, summary: string) => string;
58
64
  }
59
65
  /** Host-visible record of one claim. */
60
66
  export interface ToldClaim {
@@ -80,6 +86,12 @@ export interface AdviceGiven {
80
86
  /** For the listener's next decision; the voice is the speaker. */
81
87
  suggestion: Suggestion;
82
88
  }
89
+ export interface WithheldClaim {
90
+ prop: string;
91
+ reason: string;
92
+ /** Present only when a host phrasing callback rendered this reason. */
93
+ summary?: string;
94
+ }
83
95
  export interface ConversationResult {
84
96
  /** Testimony percepts for `perceive(listener, told)`. */
85
97
  told: Percept[];
@@ -87,10 +99,7 @@ export interface ConversationResult {
87
99
  advice: AdviceGiven[];
88
100
  claims: ToldClaim[];
89
101
  /** Candidate claims the speaker held back, and why (e.g. 'norm:backbiting', 'norm:lying'). */
90
- withheld: {
91
- prop: string;
92
- reason: string;
93
- }[];
102
+ withheld: WithheldClaim[];
94
103
  /** True when any told claim was a lie. */
95
104
  deceit: boolean;
96
105
  /** The speaker's own deed tags for conscience (e.g. backbiting/lying violated), deduplicated, sorted. */
@@ -21,6 +21,10 @@
21
21
  * share of conversation about absent third parties that spreads reputational information (Dunbar 2004), and
22
22
  * extraversion as talkativeness (how many claims are offered).
23
23
  *
24
+ * SCOPE (host options): `loyalWarning` admits only unfavourable listener claims when affection is at least
25
+ * 0.5 (an engineering threshold). `confidentMin` sets a per-call relay threshold. `phrase` renders selected
26
+ * testimony only; the host owns authored facts, delivery and subsequent disclosure. No hidden relay occurs.
27
+ *
24
28
  * It does NOT generate language, model turn-taking, persuasion beyond source trust, what the speaker believes
25
29
  * the listener already knows, lies of omission, white lies told to spare feelings, or the speaker's fear of
26
30
  * being found out. Coefficients are engineering defaults, not calibrated estimates. Nothing here judges the
@@ -150,12 +154,25 @@ export function converse(speaker, listener, ctx) {
150
154
  if (b.prop.startsWith('advice:') || b.prop.includes(':should:'))
151
155
  continue;
152
156
  const subject = b.prop.split(':')[0];
153
- if (subject === listener.id || subject === speaker.id)
157
+ if (subject === speaker.id)
154
158
  continue;
155
159
  const cred = expit(b.logOdds);
156
- if (Math.abs(cred - 0.5) < C.confidentMin)
160
+ const threshold = Number.isFinite(ctx.confidentMin) &&
161
+ ctx.confidentMin >= 0 &&
162
+ ctx.confidentMin <= 0.5
163
+ ? ctx.confidentMin
164
+ : C.confidentMin;
165
+ if (Math.abs(cred - 0.5) < threshold)
157
166
  continue;
158
167
  const value = cred > 0.5;
168
+ if (subject === listener.id) {
169
+ const trait = parseTraitProp(b.prop, table)?.trait;
170
+ if (!ctx.loyalWarning ||
171
+ relationshipWith(speaker, listener.id).affection < 0.5 ||
172
+ trait === undefined ||
173
+ !isUnfavourableTrait(trait, value, table))
174
+ continue;
175
+ }
159
176
  const confidence = value ? cred : 1 - cred;
160
177
  const recency = 1 / (1 + Math.max(0, ctx.at - b.updatedAt) / C.recencyScale);
161
178
  consider(b.prop, value, confidence, cred, false, R.confidence * (confidence - 0.5) * 2 + R.recency * recency);
@@ -231,6 +248,8 @@ export function converse(speaker, listener, ctx) {
231
248
  ? `${nameOf(speaker)} says ${c.about} is ${c.value ? '' : 'not '}${c.prop.split(':')[1] ?? ''}`
232
249
  : `${nameOf(speaker)} says ${c.prop} is ${c.value ? 'so' : 'not so'}`,
233
250
  };
251
+ if (ctx.phrase)
252
+ pc.summary = ctx.phrase(Object.freeze(structuredClone(c)), pc.summary ?? '');
234
253
  const claim = pc.claims?.[0];
235
254
  if (claim && c.origin !== undefined)
236
255
  claim.origin = c.origin;
@@ -292,6 +311,9 @@ export function converse(speaker, listener, ctx) {
292
311
  suggestion.appeal = 'benevolence';
293
312
  out.advice.push({ action: a.action, percept, suggestion });
294
313
  }
314
+ if (ctx.phrase)
315
+ for (const withheld of out.withheld)
316
+ withheld.summary = ctx.phrase(Object.freeze(structuredClone(withheld)), withheld.reason);
295
317
  const magnitude = clamp01((ctx.magnitude ?? C.chatMagnitude) + C.chatPerClaim * out.told.length);
296
318
  out.socialEvents.speaker.push({ at: ctx.at, kind: 'chat', otherId: listener.id, byMe: true, magnitude });
297
319
  out.socialEvents.listener.push({ at: ctx.at, kind: 'chat', otherId: speaker.id, byMe: false, magnitude });
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /** Human Framework public surface. */
2
- export declare const FRAMEWORK_VERSION = "2.15.0";
2
+ export declare const FRAMEWORK_VERSION = "2.17.0";
3
3
  export type { CrisisEvent, PracticeKind } from './affect/index.ts';
4
4
  export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
5
5
  export type { GoalTemplate, MakeUpReceipt, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
6
- export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.ts';
6
+ export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.ts';
7
7
  export * from './beliefs/index.ts';
8
8
  export * from './body/index.ts';
9
9
  export * from './character/index.ts';
@@ -31,7 +31,8 @@ export * from './partnering/index.ts';
31
31
  export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.ts';
32
32
  export * from './person.ts';
33
33
  export * from './persona.ts';
34
- export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.ts';
34
+ export * from './persona-calendar.ts';
35
+ export { adoptProjectTask, considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal, } from './projects/index.ts';
35
36
  export * from './promptings/index.ts';
36
37
  export type { ConsequenceEvidence, PlanRoute, PlanStep } from './prospection/index.ts';
37
38
  export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.ts';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /** Human Framework public surface. */
2
- export const FRAMEWORK_VERSION = '2.15.0';
2
+ export const FRAMEWORK_VERSION = '2.17.0';
3
3
  export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
4
4
  // `agenda.release` collides with `affect.release`; the agenda one is re-exported under a clearer name.
5
- export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
5
+ export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
6
6
  export * from './beliefs/index.js';
7
7
  export * from './body/index.js';
8
8
  export * from './character/index.js';
@@ -28,7 +28,8 @@ export * from './partnering/index.js';
28
28
  export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.js';
29
29
  export * from './person.js';
30
30
  export * from './persona.js';
31
- export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.js';
31
+ export * from './persona-calendar.js';
32
+ export { adoptProjectTask, considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal, } from './projects/index.js';
32
33
  export * from './promptings/index.js';
33
34
  export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.js';
34
35
  export { fingerprint, restore, snapshot } from './restore.js';
package/dist/migrate.js CHANGED
@@ -71,6 +71,11 @@ export const MIGRATIONS = {
71
71
  note: 'the surroundings field `ambient.now` is renamed `ambient.percept`; per-person retention is optional and starts absent; behaviour unchanged',
72
72
  apply: renameAmbient,
73
73
  },
74
+ '2.14.1': {
75
+ to: '2.17.0',
76
+ note: 'version stamp only; project protection and narration/social options require explicit opt-in, and existing enabled faculties retain exact behavior',
77
+ apply: stamp,
78
+ },
74
79
  '2.14.0': {
75
80
  to: '2.14.1',
76
81
  note: 'version stamp only; restored projects and persistence retain 2.14.0 behaviour until explicitly re-enabled',
@@ -34,6 +34,23 @@ export const EN_LINES = {
34
34
  // Decision lines by other dominant sources.
35
35
  'source:habit': ['Same as always.', 'Out of habit, really.', "It's what I do at this hour."],
36
36
  'source:effort': ['The easy thing, for now.', 'Nothing strenuous.'],
37
+ 'review.cause': [
38
+ 'After {cause}, {missed} remained undone. {next}',
39
+ 'I left {missed} unfinished after {cause}. {next}',
40
+ 'I was {cause}; {missed} went unfinished. {next}',
41
+ ],
42
+ 'review.accounting': [
43
+ "I didn't keep {missed}. Next time I'll be ready when it comes.",
44
+ 'I left {missed} undone. I will prepare for the next occasion.',
45
+ 'Today {missed} went unfinished. Next time I will make room for it.',
46
+ ],
47
+ 'review.ruminating': [
48
+ 'I keep going back over {missed}.',
49
+ 'My thoughts keep returning to {missed}.',
50
+ 'I cannot stop turning {missed} over in my mind.',
51
+ ],
52
+ 'source:material.named': ['It brings {material}.', 'We can use the {material}.'],
53
+ 'intention.material.named': ['for {material}'],
37
54
  'source:material': ['There is money in it.', 'It pays.'],
38
55
  'source:expectation': ['It went well last time.', 'I know how this goes.'],
39
56
  'source:conscience:repair': ['I owe them that much.', 'I have to make this right.'],
@@ -363,6 +380,12 @@ function regularGerund(verb) {
363
380
  export function actionForms(action, lexicon) {
364
381
  const given = lexicon?.actions?.[action];
365
382
  const base = given?.base ?? action.replace(/[-_]/g, ' ');
383
+ if (lexicon?.nounActions)
384
+ return {
385
+ base: given?.base ?? `do ${base}`,
386
+ past: given?.past ?? `did ${base}`,
387
+ gerund: given?.gerund ?? `doing ${base}`,
388
+ };
366
389
  const [verb = base, ...rest] = base.split(' ');
367
390
  const tail = rest.length > 0 ? ` ${rest.join(' ')}` : '';
368
391
  return {
@@ -1,4 +1,4 @@
1
- import type { DecisionRecord, Lexicon, Person, SuggestionResolution } from '../types.ts';
1
+ import type { DecisionRecord, Episode, Lexicon, Person, SuggestionResolution } from '../types.ts';
2
2
  /** Intention string for a decision: 'for Allah', 'to feed myself', 'to keep my promise to X', ... */
3
3
  export declare function intentionFor(p: Person, record: DecisionRecord, lexicon?: Lexicon): string;
4
4
  /** First-person narration of a decision record. Deterministic by the decision id. */
@@ -7,3 +7,9 @@ export declare function narrateDecision(p: Person, record: DecisionRecord, lexic
7
7
  export declare function voiceLine(p: Person, res: SuggestionResolution, key?: string, lexicon?: Lexicon): string;
8
8
  /** Short UI summary of a person's state. */
9
9
  export declare function describePerson(p: Person, lexicon?: Lexicon): string;
10
+ /**
11
+ * SCOPE: a host-authored addressee form of an episode, selected only for its named actor.
12
+ * Free text cannot safely be inflected. With no authored form, return the original summary.
13
+ * This read-only helper neither recalls an episode nor changes its salience or RNG.
14
+ */
15
+ export declare function recallLine(episode: Episode, toActor: string): string;
@@ -93,7 +93,9 @@ export function intentionFor(p, record, lexicon) {
93
93
  case 'emotion':
94
94
  return phraseLine('intention.emotion', {}, lex);
95
95
  case 'material':
96
- return phraseLine('intention.material', {}, lex);
96
+ return lex?.materialMeaning !== undefined
97
+ ? phraseLine('intention.material.named', { material: lex.materialMeaning }, lex)
98
+ : phraseLine('intention.material', {}, lex);
97
99
  case 'precommit':
98
100
  return phraseLine('intention.precommit', {}, lex);
99
101
  case 'expectation':
@@ -157,6 +159,8 @@ export function narrateDecision(p, record, lexicon) {
157
159
  ? pickLine(`emotion:${tag}`, id, {}, lex)
158
160
  : pickLine('emotion:default', id, {}, lex));
159
161
  }
162
+ else if (kind === 'material' && lex?.materialMeaning !== undefined)
163
+ parts.push(pickLine('source:material.named', id, { material: lex.materialMeaning }, lex));
160
164
  else
161
165
  parts.push(linesFor(`source:${source}`, lex).length > 0
162
166
  ? pickLine(`source:${source}`, id, {}, lex)
@@ -192,6 +196,8 @@ export function narrateDecision(p, record, lexicon) {
192
196
  /** The person's spoken reply to a suggestion. */
193
197
  export function voiceLine(p, res, key = res.reason, lexicon) {
194
198
  const lex = lexicon ?? p.lexicon;
199
+ if (lex?.variety && (res.verdict === 'assented' || res.verdict === 'deferred'))
200
+ key = `${p.id}:${p.now}:${p.traits.extraversion}:${p.traits.honesty}:${key}`;
195
201
  const trust = p.will.voices.find((v) => v.voiceId === res.voiceId)?.trust ?? 0.5;
196
202
  switch (res.verdict) {
197
203
  case 'assented':
@@ -293,3 +299,13 @@ export function describePerson(p, lexicon) {
293
299
  const state = bits.length > 0 ? bits.join(', ') : phraseLine('describe.well', {}, lex);
294
300
  return phraseLine('describe.line', { name: p.name, act, state }, lex);
295
301
  }
302
+ /**
303
+ * SCOPE: a host-authored addressee form of an episode, selected only for its named actor.
304
+ * Free text cannot safely be inflected. With no authored form, return the original summary.
305
+ * This read-only helper neither recalls an episode nor changes its salience or RNG.
306
+ */
307
+ export function recallLine(episode, toActor) {
308
+ return episode.actorId === toActor && episode.addressedSummary !== undefined
309
+ ? episode.addressedSummary
310
+ : episode.summary;
311
+ }
package/dist/person.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type BodyForecastSpan } from './body/index.ts';
2
2
  import { type NafsPattern } from './promptings/index.ts';
3
3
  import { type SkillTransfer } from './skills/index.ts';
4
+ import { applyReputationBelief } from './social/index.ts';
4
5
  import type { Activity, Affordance, BodyLoad, BodyReadout, Command, Commitment, DecisionRecord, LearningDomain, Minute, NeedId, NeedReading, NormDefinition, Outcome, Percept, Person, PersonSpec, Retention, Suggestion, SuggestionResolution, Unit } from './types.ts';
5
6
  import { type CommandOutcome } from './will/index.ts';
6
7
  export declare const PERSON_DEFAULTS: {
@@ -85,12 +86,16 @@ export declare function standUp(p: Person): boolean;
85
86
  * only age (from `life.bornAt`) changes. Hosts that want the interval lived call `tick`.
86
87
  */
87
88
  export declare function skip(p: Person, to: Minute): void;
89
+ export interface PerceiveOptions {
90
+ /** Complete host character-trait vocabulary; absent uses HF's default table. */
91
+ reputationTraits?: Parameters<typeof applyReputationBelief>[5];
92
+ }
88
93
  /**
89
94
  * Attend to percepts (limited capacity), then encode what got through: claims into beliefs, interactions
90
95
  * into relationships, others' acts judged by my norms, appraisal into emotions, and an episode each.
91
96
  * Returns the percepts that were attended to.
92
97
  */
93
- export declare function perceive(p: Person, percepts: readonly Percept[]): Percept[];
98
+ export declare function perceive(p: Person, percepts: readonly Percept[], options?: PerceiveOptions): Percept[];
94
99
  export interface DecideOptions {
95
100
  suggestion?: Suggestion;
96
101
  /** Several voices in one decision (N1); merged with `suggestion`, one per voice id. */
package/dist/person.js CHANGED
@@ -38,7 +38,7 @@ import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
38
38
  import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
39
39
  import { widow } from './partnering/index.js';
40
40
  import { beginPursuit, enablePersistence, releasePursuit } from './persistence/index.js';
41
- import { considerProjects, enableProjects, noteProjectStatus, perceiveProspect } from './projects/index.js';
41
+ import { considerProjects, enableProjects, noteProjectAct, noteProjectStatus, perceiveProspect, } from './projects/index.js';
42
42
  import { enablePromptings, nafsPatterns, notePromptings } from './promptings/index.js';
43
43
  import { bodyForecastFor, consumeConsequences, enablePlanning, forecastLine, forecastThresholds, pruneConsequences, receiveConsequences, } from './prospection/index.js';
44
44
  import { enableReview, reviewIfDue } from './review/index.js';
@@ -585,10 +585,10 @@ function socialKindOf(kind) {
585
585
  return kind;
586
586
  return SOCIAL_ALIASES[kind];
587
587
  }
588
- function perceiveOne(p, pc) {
588
+ function perceiveOne(p, pc, options) {
589
589
  receiveConsequences(p, pc);
590
590
  if (p.projects && pc.prospect)
591
- perceiveProspect(p, pc.prospect, pc.at);
591
+ perceiveProspect(p, pc.prospect, pc.at, (pc.channel === 'saw' || pc.channel === 'outcome') && (pc.actorId === undefined || pc.actorId === p.id));
592
592
  const at = Math.min(Math.max(pc.at, p.affect.lastUpdated), p.now);
593
593
  const actor = pc.actorId;
594
594
  const target = pc.targetId;
@@ -606,7 +606,7 @@ function perceiveOne(p, pc) {
606
606
  if (claim.origin !== undefined || claim.lineage !== undefined)
607
607
  noteProvenance(p, claim, direct ? p.id : (actor ?? 'rumour'), at);
608
608
  // Hearsay about someone's character moves my relationship with them (reputation).
609
- applyReputationBelief(p, claim.prop, before, credence(p, claim.prop), at);
609
+ applyReputationBelief(p, claim.prop, before, credence(p, claim.prop), at, options.reputationTraits);
610
610
  }
611
611
  // Another source's account, reported by the teller (2.10.0): one source telling two people opposite things.
612
612
  if (pc.channel === 'told' && actor !== undefined && Array.isArray(pc.reports)) {
@@ -724,8 +724,11 @@ function perceiveOne(p, pc) {
724
724
  summary: pc.summary,
725
725
  tags: [pc.kind, pc.channel, ...(isNight(at) ? ['night'] : []), ...(threat ? ['threat'] : [])],
726
726
  };
727
- if (actor !== undefined)
727
+ if (actor !== undefined) {
728
728
  episode.actorId = actor;
729
+ if (pc.addressedSummary !== undefined)
730
+ episode.addressedSummary = pc.addressedSummary;
731
+ }
729
732
  if (target !== undefined)
730
733
  episode.targetId = target;
731
734
  if (pc.placeId !== undefined)
@@ -748,7 +751,7 @@ function judgeDeed(p, actor, pc, at) {
748
751
  * into relationships, others' acts judged by my norms, appraisal into emotions, and an episode each.
749
752
  * Returns the percepts that were attended to.
750
753
  */
751
- export function perceive(p, percepts) {
754
+ export function perceive(p, percepts, options = {}) {
752
755
  if (percepts.length === 0 || !p.body.alive)
753
756
  return [];
754
757
  const body = readBodyOf(p);
@@ -758,7 +761,7 @@ export function perceive(p, percepts) {
758
761
  fear: emotionLevel(p, 'fear'),
759
762
  });
760
763
  for (const pc of attended) {
761
- perceiveOne(p, pc);
764
+ perceiveOne(p, pc, options);
762
765
  // What I just noticed may bring a memory back (the cemetery road, her name on the phone).
763
766
  const at = Math.min(Math.max(pc.at, p.affect.lastUpdated), p.now);
764
767
  const cue = { at, action: pc.kind, tags: [pc.kind] };
@@ -1449,8 +1452,10 @@ export function finish(p, outcome, opts = {}) {
1449
1452
  ...(act.necessity ? { necessity: true } : {}),
1450
1453
  ...(opts.catalog ? { catalog: opts.catalog } : {}),
1451
1454
  });
1452
- if (p.projects)
1455
+ if (p.projects) {
1456
+ noteProjectAct(p, aff, completed, now);
1453
1457
  noteProjectStatus(p, now);
1458
+ }
1454
1459
  if (p.pursuit)
1455
1460
  releasePursuit(p);
1456
1461
  // A completed violating action inside an open abstention's window broke it (the fast, e.g. "no work after the evening prayer").
@@ -0,0 +1,39 @@
1
+ import { type PersonaAdmission, type PersonaAdmissionResult, type PersonaProjection, type PersonaProjectionSnapshot } from './persona.ts';
2
+ import { type Commitment, type HeldNorm, type NormDefinition, type OwedMakeUp, type Person } from './types.ts';
3
+ export interface PersonaCalendarMap {
4
+ version: 'hf-persona-calendar-map/1';
5
+ /** Exactly target opening minus source departure. All retained calendar timestamps use this offset. */
6
+ offset: number;
7
+ references: {
8
+ commitments: Record<string, string>;
9
+ chains: Record<string, string>;
10
+ actions: Record<string, string>;
11
+ entities: Record<string, string>;
12
+ norms: Record<string, string>;
13
+ };
14
+ sourceNorms: NormDefinition[];
15
+ }
16
+ export interface CalendarPersonaProjection extends Omit<PersonaProjection, 'commitments'> {
17
+ calendar: PersonaCalendarMap;
18
+ }
19
+ export interface CalendarPersonaSnapshot {
20
+ format: 'hf-persona-calendar/1';
21
+ person: PersonaProjectionSnapshot;
22
+ calendar: {
23
+ offset: number;
24
+ commitments: Commitment[];
25
+ owed: OwedMakeUp[];
26
+ lapse: Person['agenda']['lapse'] | null;
27
+ heldNorms: HeldNorm[];
28
+ norms: NormDefinition[];
29
+ };
30
+ }
31
+ export interface CalendarPersonaAdmission extends PersonaAdmission {
32
+ targetNorms: NormDefinition[];
33
+ }
34
+ /** Validate portable calendar data strictly; no restore defaults or debt settlement. */
35
+ export declare function validateCalendarPersona(input: unknown): CalendarPersonaSnapshot;
36
+ /** Source declares a complete semantic ID map, a clock offset and source-backed norm catalog. */
37
+ export declare function projectCalendarPersona(source: Person, mapping: CalendarPersonaProjection): CalendarPersonaSnapshot;
38
+ /** Receiver declares identical norm meanings and a clean calendar at the exact declared opening clock. */
39
+ export declare function admitCalendarPersona(input: unknown, target: Person, policy: CalendarPersonaAdmission): PersonaAdmissionResult;