genesis-compiler 1.7.7 → 1.7.8

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.
@@ -82,7 +82,25 @@ JSON on stdin:
82
82
  ```
83
83
 
84
84
  For `genesis hook turn`, the input has the same shape with `scope: "turn"`.
85
- The executable returns only the requested host contribution as text. Codex and Claude Code
85
+ The executable returns exactly one JSON result:
86
+
87
+ - `{"kind":"unclaimed"}`: this native conversation is not managed by the host;
88
+ Genesis delivers its standalone guide.
89
+ - `{"kind":"contribution","text":"..."}`: Genesis composes this bounded host
90
+ contribution with its own session guide (or bounded turn contribution).
91
+ - `{"kind":"delivery","text":"..."}`: the host owns the complete session
92
+ delivery; Genesis forwards it without adding its standalone copy. Empty text
93
+ means the host already delivers through a native system field. This result
94
+ is valid only for session scope.
95
+
96
+ Malformed results and configured-resolver failures fail the hook. An unavailable
97
+ claimed owner must fail rather than returning `unclaimed`. Ownership belongs to
98
+ the native conversation: these results do not change project hook files, so
99
+ independent CLI and managed conversations can use the same project. The host
100
+ owns durable bindings, ancestry verification and restoration across reconnection.
101
+ No host framework dependency is added to Genesis.
102
+
103
+ Codex and Claude Code
86
104
  hook input and OpenCode's transform callbacks supply the native session id;
87
105
  Genesis extracts that id but does not understand the host registry or the
88
106
  provider's prompt-placement rules. The Codex and Claude prompt hooks and OpenCode message
@@ -104,8 +122,8 @@ Codex hook settings. A host can render these fields as an actionable failure
104
122
  instead of displaying the runtime stack.
105
123
 
106
124
  The OpenCode session adapter also includes verified native `parentSessionIds`
107
- in `GENESIS_HOST_CONTEXT_INPUT` for host command shims. The resolver still
108
- receives the actual child's `providerSessionId`; Genesis does not interpret a
125
+ in `GENESIS_HOST_CONTEXT_INPUT` for host command shims and in the resolver
126
+ request when present (at most 32 IDs). The resolver receives the actual child's `providerSessionId`; Genesis does not interpret a
109
127
  host's ownership rules.
110
128
 
111
129
  After updating Genesis, hosts can inspect all generated integrations with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.7.7",
3
+ "version": "1.7.8",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -426,8 +426,8 @@ function writeResult(command, result) {
426
426
  line(process.stdout, `${command}: ${result.status}`);
427
427
  }
428
428
 
429
- async function hookSessionId(environment = process.env) {
430
- if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return null;
429
+ async function hookSessionInput(environment = process.env) {
430
+ if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return {};
431
431
  let source = environment[HOST_CONTEXT_INPUT_ENV];
432
432
  if (source === undefined && !process.stdin.isTTY) {
433
433
  const chunks = [];
@@ -441,7 +441,7 @@ async function hookSessionId(environment = process.env) {
441
441
  }
442
442
  source = Buffer.concat(chunks).toString('utf8');
443
443
  }
444
- if (!String(source || '').trim()) return null;
444
+ if (!String(source || '').trim()) return {};
445
445
  let input;
446
446
  try {
447
447
  input = JSON.parse(source);
@@ -449,7 +449,15 @@ async function hookSessionId(environment = process.env) {
449
449
  fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input must contain JSON.');
450
450
  }
451
451
  const sessionId = input?.sessionId ?? input?.session_id;
452
- return typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
452
+ const parentSessionIds = input?.parentSessionIds ?? [];
453
+ if (!Array.isArray(parentSessionIds) || parentSessionIds.length > 32 ||
454
+ parentSessionIds.some(id => typeof id !== 'string' || !id.trim())) {
455
+ fail('HOST_CONTEXT_INPUT_INVALID', 'Native parent session ids must be a bounded list of strings.');
456
+ }
457
+ return {
458
+ providerSessionId: typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null,
459
+ ...(parentSessionIds.length ? { parentSessionIds } : {}),
460
+ };
453
461
  }
454
462
 
455
463
  async function cliStackPackages(projectRoot, supplied) {
@@ -574,19 +582,24 @@ async function execute({ command, operands, options }, { signal } = {}) {
574
582
  if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
575
583
  return { kind: 'session', status: 'ready', output: '' };
576
584
  }
577
- const providerSessionId = await hookSessionId();
585
+ const nativeSession = await hookSessionInput();
578
586
  const hostContext = await resolveConfiguredHostContext({
579
587
  projectRoot,
580
- providerSessionId,
588
+ ...nativeSession,
581
589
  scope,
582
590
  });
591
+ // Ownership is resolved for this native conversation, not for the project.
592
+ // A host-owned delivery already contains everything its runtime requires.
593
+ if (hostContext?.kind === 'delivery') {
594
+ return { kind: 'session', status: 'ready', output: hostContext.text };
595
+ }
583
596
  if (scope === 'turn') {
584
597
  return hostContext === null
585
598
  ? { kind: 'turn', status: 'ready', output: '' }
586
599
  : {
587
600
  kind: 'turn',
588
601
  ...await projectTurnContext({
589
- hostDriver: () => hostContext,
602
+ hostDriver: () => hostContext.text,
590
603
  hostDriverInput: { scope: 'turn' },
591
604
  }),
592
605
  };
@@ -595,7 +608,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
595
608
  kind: 'session',
596
609
  ...await projectSessionContext({
597
610
  ...(hostContext === null ? {} : {
598
- hostDriver: () => hostContext,
611
+ hostDriver: () => hostContext.text,
599
612
  hostDriverInput: { scope: 'session' },
600
613
  }),
601
614
  projectRoot,
@@ -55,6 +55,7 @@ export async function resolveConfiguredHostContext({
55
55
  environment = process.env,
56
56
  projectRoot,
57
57
  providerSessionId = null,
58
+ parentSessionIds = [],
58
59
  scope = 'session',
59
60
  } = {}) {
60
61
  const command = configuredResolver(environment);
@@ -66,8 +67,20 @@ export async function resolveConfiguredHostContext({
66
67
  input: {
67
68
  scope,
68
69
  providerSessionId: providerSessionId || null,
70
+ ...(parentSessionIds.length ? { parentSessionIds } : {}),
69
71
  data: resolverData(environment),
70
72
  },
71
73
  });
72
- return output;
74
+ let result;
75
+ try { result = JSON.parse(output); } catch {
76
+ throw resolverError('The host context resolver must return a JSON delivery result.');
77
+ }
78
+ if (!result || typeof result !== 'object' || Array.isArray(result) ||
79
+ !['unclaimed', 'contribution', 'delivery'].includes(result.kind) ||
80
+ Object.keys(result).some((key) => !['kind', 'text'].includes(key)) ||
81
+ (result.kind === 'unclaimed' ? Object.hasOwn(result, 'text') : typeof result.text !== 'string') ||
82
+ (result.kind === 'delivery' && scope !== 'session')) {
83
+ throw resolverError('Invalid host context delivery result.');
84
+ }
85
+ return result.kind === 'unclaimed' ? null : result;
73
86
  }