@atolis-hq/wake 0.4.30 → 0.4.31

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.
@@ -4,7 +4,7 @@ import { join } from 'node:path';
4
4
  import { ControlStreamKind, ScheduleService, createIntakePipeline, createRunnerPipeline, createWorkCancellationPolicy, } from '../control-plane/index.js';
5
5
  import { RunRepository, createRuntimeMemoryProfile, } from '../execution/index.js';
6
6
  import { AgentRunPublicationReactor, ArtifactRegistrationReactor, DeliveryOutcomeReactor, DeliveryService, IntegrationStreamKind, PollService, ProviderRegistry, deliveryProjectionDefinitions, fakeProviderDefinition, } from '../integrations/index.js';
7
- import { compileWorkflowSelectors, createPullRequestTransitionEvidence, createResourceTransitionReactor, createWatchReactor, createWatchReconciler, selectWorkflow, workflowName, } from '../orchestration/index.js';
7
+ import { compileWorkflowSelectors, createConversationCommandReactor, createPullRequestTransitionEvidence, createResourceTransitionReactor, createWatchReactor, createWatchReconciler, selectWorkflow, workflowName, } from '../orchestration/index.js';
8
8
  import { BuiltInResourceCapability, resourceId, } from '../resources/index.js';
9
9
  import { hydrateFakeProviderEvidence } from './fake-provider-files.js';
10
10
  import { createRuntimeProjectionSubscriptions, } from './projection-runtime.js';
@@ -109,6 +109,7 @@ export async function composeIntegrationRuntime(input) {
109
109
  return operation();
110
110
  });
111
111
  const outcomes = new DeliveryOutcomeReactor(input.journal, input.orchestration, input.projections, input.processorState, input.conversations, input.subscriptionRunSerialiser);
112
+ const conversationCommands = createConversationCommandReactor(input.orchestration);
112
113
  // Only poll hits a rate-limited external API, so only this half of the
113
114
  // Tick needs a backing-off host; see bootstrap/surface-cli-applications.ts.
114
115
  // Inbound translation reads durable provider facts through its own services,
@@ -163,6 +164,7 @@ export async function composeIntegrationRuntime(input) {
163
164
  artifacts.processor,
164
165
  outcomes.processor,
165
166
  agentRunPublications.processor,
167
+ conversationCommands.processor,
166
168
  ...providers.map((provider) => provider.inbound.processor),
167
169
  ],
168
170
  providers,
@@ -124,6 +124,6 @@ export function resolveWakeVersion(options = {}) {
124
124
  return `g${headHash.slice(0, 7)}`;
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "v0.4.30";
127
+ export const wakeVersion = "v0.4.31";
128
128
  /** The registry-installable version used by packaged sandbox images. */
129
129
  export const wakePackageVersion = packageVersion(defaultRepoRoot) ?? wakeVersion;
@@ -39,6 +39,8 @@ const origin = z.discriminatedUnion('kind', [
39
39
  resourceId: z.string().min(1),
40
40
  threadId: z.string().min(1),
41
41
  messageId: z.string().min(1),
42
+ authorized: z.boolean().optional(),
43
+ capabilities: z.array(z.string().min(1)).readonly().optional(),
42
44
  location: z
43
45
  .object({
44
46
  path: z.string().min(1),
@@ -3,3 +3,4 @@ import type { WorkItemId } from '../../work/index.js';
3
3
  export type ConversationId = Brand<string, 'ConversationId'>;
4
4
  export declare function conversationId(value: string): ConversationId;
5
5
  export declare function conversationIdForWorkItem(workItemId: WorkItemId): ConversationId;
6
+ export declare function workItemIdForConversation(conversationIdValue: ConversationId): WorkItemId;
@@ -6,3 +6,6 @@ export function conversationId(value) {
6
6
  export function conversationIdForWorkItem(workItemId) {
7
7
  return conversationId(`conversation-${String(workItemId).slice('work-'.length)}`);
8
8
  }
9
+ export function workItemIdForConversation(conversationIdValue) {
10
+ return `work-${String(conversationIdValue).slice('conversation-'.length)}`;
11
+ }
@@ -16,6 +16,10 @@ export type ConversationEntryOrigin = {
16
16
  readonly resourceId: string;
17
17
  readonly threadId: string;
18
18
  readonly messageId: string;
19
+ /** Authority established by the receiving surface before the entry is recorded. */
20
+ readonly authorized?: boolean | undefined;
21
+ /** Commands available to this entry's receiving surface. */
22
+ readonly capabilities?: readonly string[] | undefined;
19
23
  readonly location?: {
20
24
  readonly path: string;
21
25
  readonly line: number;
@@ -75,7 +75,6 @@ export declare class InboundTranslator {
75
75
  private recordConversationDeferred;
76
76
  private appendConversationRecordFact;
77
77
  private recordConversationEntry;
78
- private applyConversationCommand;
79
78
  private failureRecorded;
80
79
  private retryRecorded;
81
80
  private recordTranslationRecovery;
@@ -110,7 +110,8 @@ export class InboundTranslator {
110
110
  if (isGitHubAdapterEventType(owned, GitHubEventType.WorkObserved))
111
111
  await this.apply(owned);
112
112
  if (isGitHubAdapterEventType(owned, GitHubEventType.CommentObserved)) {
113
- if (!(await this.suppressWorkItemEffects(owned)))
113
+ if (owned.event.payload.reviewKind === 'formal' &&
114
+ !(await this.suppressWorkItemEffects(owned)))
114
115
  await applyReviewSignal({
115
116
  event: owned,
116
117
  journal: this.journal,
@@ -226,7 +227,7 @@ export class InboundTranslator {
226
227
  entry.origin.messageId === externalId);
227
228
  if (existing?.entries.some((entry) => entry.representations.some((representation) => representation.resourceId === resource.resourceId &&
228
229
  representation.externalId === externalId)))
229
- return this.applyConversationCommand(correlation.workItemId, event);
230
+ return;
230
231
  if (priorEntry !== undefined) {
231
232
  if (priorEntry.body !== event.event.payload.body)
232
233
  await this.conversations.revise({
@@ -234,7 +235,7 @@ export class InboundTranslator {
234
235
  entryId: priorEntry.entryId,
235
236
  body: event.event.payload.body,
236
237
  }, commandContext(event));
237
- return this.applyConversationCommand(correlation.workItemId, event);
238
+ return;
238
239
  }
239
240
  await this.conversations.record({
240
241
  conversationId,
@@ -247,24 +248,14 @@ export class InboundTranslator {
247
248
  resourceId: resource.resourceId,
248
249
  threadId: resource.externalKey.key,
249
250
  messageId: externalId,
251
+ authorized: isAuthorizedConversationActor(event),
252
+ capabilities: this.conversationCapabilities,
250
253
  ...(event.event.payload.reviewKind !== 'issue' ||
251
254
  event.event.payload.location === undefined
252
255
  ? {}
253
256
  : { location: event.event.payload.location }),
254
257
  },
255
258
  }, commandContext(event));
256
- await this.applyConversationCommand(correlation.workItemId, event);
257
- }
258
- async applyConversationCommand(workItemId, event) {
259
- // Native formal reviews already carry their own verified decision and workflow signal.
260
- if (event.event.payload.reviewKind !== 'issue')
261
- return;
262
- await this.orchestration?.applyConversationCommand(workItemId, {
263
- body: event.event.payload.body,
264
- actorId: event.event.payload.actor.id,
265
- capabilities: this.conversationCapabilities,
266
- authorized: isAuthorizedConversationActor(event),
267
- }, commandContext(event));
268
259
  }
269
260
  async failureRecorded(sourceEventId) {
270
261
  return (await this.journal.readStream(integrationStream(this.adapter))).some((event) => {
@@ -0,0 +1,6 @@
1
+ import { type EventProcessor } from '@atolis-hq/eventing';
2
+ import type { OrchestrationService } from './orchestration-service.js';
3
+ /** Applies human commands only after their external message is canonical conversation history. */
4
+ export declare function createConversationCommandReactor(orchestration: OrchestrationService): {
5
+ readonly processor: EventProcessor;
6
+ };
@@ -0,0 +1,59 @@
1
+ import { EventActorKind, EventProcessorCategory, EventProcessorReplayPolicy, correlationId, defineEventProcessor, } from '@atolis-hq/eventing';
2
+ import { ConversationEventType, ConversationOriginKind, selectConversationEvent, workItemIdForConversation, } from '../../conversations/index.js';
3
+ import { conversationCommand } from './conversation-command.js';
4
+ /** Applies human commands only after their external message is canonical conversation history. */
5
+ export function createConversationCommandReactor(orchestration) {
6
+ return {
7
+ processor: defineEventProcessor({
8
+ consumer: 'reactor:orchestration.conversation-command',
9
+ name: 'orchestration.conversation-command',
10
+ owner: 'orchestration',
11
+ category: EventProcessorCategory.Reactor,
12
+ replayPolicy: EventProcessorReplayPolicy.Idempotent,
13
+ select(event) {
14
+ const conversation = selectConversationEvent(event);
15
+ if (conversation?.event.eventType !== ConversationEventType.EntryRecorded)
16
+ return null;
17
+ return {
18
+ conversationId: conversation.stream.id,
19
+ eventId: conversation.event.eventId,
20
+ correlationId: conversation.event.correlationId,
21
+ occurredAt: conversation.event.occurredAt,
22
+ entry: conversation.event.payload,
23
+ };
24
+ },
25
+ async handle(event) {
26
+ if (event === null)
27
+ return;
28
+ const { entry } = event;
29
+ if (entry.origin.kind !== ConversationOriginKind.External)
30
+ return;
31
+ const workItemId = workItemIdForConversation(event.conversationId);
32
+ const input = {
33
+ body: entry.body,
34
+ actorId: entry.origin.actorId,
35
+ capabilities: (entry.origin.capabilities ?? []),
36
+ authorized: entry.origin.authorized === true,
37
+ };
38
+ await orchestration.applyConversationCommand(workItemId, input, {
39
+ commandId: `${event.eventId}:conversation-command`,
40
+ correlationId: correlationId(event.correlationId),
41
+ occurredAt: event.occurredAt,
42
+ actor: { kind: EventActorKind.Integration, id: entry.origin.adapter },
43
+ });
44
+ if (conversationCommand(entry.body) !== null || !input.authorized)
45
+ return;
46
+ for (const workflow of await orchestration.listAll()) {
47
+ if (workflow.workItemId !== workItemId)
48
+ continue;
49
+ await orchestration.resumeBlockedStageForChanges(workflow.workflowInstanceId, {
50
+ commandId: `${event.eventId}:conversation-reply`,
51
+ correlationId: correlationId(event.correlationId),
52
+ occurredAt: event.occurredAt,
53
+ actor: { kind: EventActorKind.Integration, id: entry.origin.adapter },
54
+ });
55
+ }
56
+ },
57
+ }),
58
+ };
59
+ }
@@ -11,6 +11,7 @@ export * from './application/signal-reactor.js';
11
11
  export * from './application/watch-reactor.js';
12
12
  export * from './application/trigger-workflow-instance.js';
13
13
  export * from './application/conversation-command.js';
14
+ export * from './application/conversation-command-reactor.js';
14
15
  export * from './application/workflow-definition-registry.js';
15
16
  export * from './contracts/activity-outcome.js';
16
17
  export * from './contracts/commands.js';
@@ -11,6 +11,7 @@ export * from './application/signal-reactor.js';
11
11
  export * from './application/watch-reactor.js';
12
12
  export * from './application/trigger-workflow-instance.js';
13
13
  export * from './application/conversation-command.js';
14
+ export * from './application/conversation-command-reactor.js';
14
15
  export * from './application/workflow-definition-registry.js';
15
16
  export * from './contracts/activity-outcome.js';
16
17
  export * from './contracts/commands.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.4.30",
3
+ "version": "0.4.31",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {