@atolis-hq/wake 0.4.15 → 0.4.16
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/dist/src/bootstrap/config/root-schema.d.ts +13 -0
- package/dist/src/bootstrap/config/root-schema.js +5 -1
- package/dist/src/bootstrap/surface-api-work-applications.js +13 -2
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/integrations/contracts/config.d.ts +13 -0
- package/dist/src/integrations/contracts/config.js +7 -0
- package/dist/src/integrations/contracts/provider.d.ts +4 -1
- package/dist/src/integrations/contracts/provider.js +3 -1
- package/dist/src/integrations/github/application/inbound-comment-syntax.d.ts +1 -1
- package/dist/src/integrations/github/application/inbound-comment-syntax.js +3 -1
- package/dist/src/integrations/github/application/inbound-review-signals.d.ts +2 -0
- package/dist/src/integrations/github/application/inbound-review-signals.js +17 -4
- package/dist/src/integrations/github/application/inbound-translator.d.ts +4 -1
- package/dist/src/integrations/github/application/inbound-translator.js +28 -4
- package/dist/src/integrations/github/contracts/events.d.ts +4 -0
- package/dist/src/integrations/github/contracts/events.js +1 -0
- package/dist/src/integrations/github/infrastructure/comment-source.js +6 -5
- package/dist/src/integrations/github/infrastructure/issue-source.d.ts +1 -1
- package/dist/src/integrations/github/infrastructure/issue-source.js +2 -0
- package/dist/src/integrations/github/provider.js +4 -1
- package/dist/src/orchestration/application/conversation-command.d.ts +22 -0
- package/dist/src/orchestration/application/conversation-command.js +46 -0
- package/dist/src/orchestration/application/orchestration-repository.js +10 -0
- package/dist/src/orchestration/application/orchestration-service.d.ts +10 -1
- package/dist/src/orchestration/application/orchestration-service.js +48 -1
- package/dist/src/orchestration/contracts/config.d.ts +5 -0
- package/dist/src/orchestration/contracts/config.js +6 -1
- package/dist/src/orchestration/contracts/vocabulary.d.ts +15 -0
- package/dist/src/orchestration/contracts/vocabulary.js +13 -0
- package/dist/src/orchestration/index.d.ts +1 -0
- package/dist/src/orchestration/index.js +1 -0
- package/package.json +1 -1
|
@@ -340,13 +340,26 @@ export declare const rootConfigSchema: z.ZodObject<{
|
|
|
340
340
|
integrations: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodObject<{
|
|
341
341
|
provider: z.ZodOptional<z.ZodString>;
|
|
342
342
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
343
|
+
conversation: z.ZodOptional<z.ZodObject<{
|
|
344
|
+
capabilities: z.ZodReadonly<z.ZodArray<z.ZodEnum<{
|
|
345
|
+
"review-surface": "review-surface";
|
|
346
|
+
"chat-surface": "chat-surface";
|
|
347
|
+
"operator-surface": "operator-surface";
|
|
348
|
+
}>>>;
|
|
349
|
+
}, z.core.$strict>>;
|
|
343
350
|
}, z.core.$loose>, z.ZodTransform<{
|
|
344
351
|
enabled: boolean;
|
|
345
352
|
provider?: string | undefined;
|
|
353
|
+
conversation?: {
|
|
354
|
+
capabilities: readonly ("review-surface" | "chat-surface" | "operator-surface")[];
|
|
355
|
+
} | undefined;
|
|
346
356
|
}, {
|
|
347
357
|
[x: string]: unknown;
|
|
348
358
|
enabled: boolean;
|
|
349
359
|
provider?: string | undefined;
|
|
360
|
+
conversation?: {
|
|
361
|
+
capabilities: readonly ("review-surface" | "chat-surface" | "operator-surface")[];
|
|
362
|
+
} | undefined;
|
|
350
363
|
}>>>>;
|
|
351
364
|
surfaces: z.ZodPipe<z.ZodDefault<z.ZodObject<{
|
|
352
365
|
api: z.ZodDefault<z.ZodObject<{
|
|
@@ -35,7 +35,11 @@ const orchestrationConfigSchema = z
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
|
-
.default({
|
|
38
|
+
.default({
|
|
39
|
+
workflows: {},
|
|
40
|
+
workflowSelectors: [],
|
|
41
|
+
default: 'default',
|
|
42
|
+
});
|
|
39
43
|
const transcriptsConfigSchema = z
|
|
40
44
|
.object({
|
|
41
45
|
enabled: z.boolean().default(false),
|
|
@@ -2,7 +2,7 @@ import { correlationId, EventActorKind } from '@atolis-hq/eventing';
|
|
|
2
2
|
import { pullRequestProjection } from '../activities/index.js';
|
|
3
3
|
import { conversationIdForWorkItem, ConversationOriginKind, } from '../conversations/index.js';
|
|
4
4
|
import { executionProjection, runsByWorkflowInstanceProjection, } from '../execution/index.js';
|
|
5
|
-
import { ApprovalAuthorityKind, GroupBudgetExtensionIneligibleError, isGroupBudgetExtensionEligible, OperatorRetryIneligibleError, orchestrationProjection, selectOperatorRetryTarget, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
5
|
+
import { ApprovalAuthorityKind, ConversationSurfaceCapability, GroupBudgetExtensionIneligibleError, isGroupBudgetExtensionEligible, OperatorRetryIneligibleError, orchestrationProjection, selectOperatorRetryTarget, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
6
6
|
import { workCorrelationsProjection, } from '../resources/index.js';
|
|
7
7
|
import { ApiCommandStatus, fromWorkItemKey, presentResource, presentRun, presentWorkflowInstance, presentWorkItem, } from '../surfaces/index.js';
|
|
8
8
|
import { workItemId, WorkStatus } from '../work/index.js';
|
|
@@ -134,7 +134,18 @@ export function createSurfaceWorkApplications(root, now) {
|
|
|
134
134
|
body: command.body,
|
|
135
135
|
origin: { kind: ConversationOriginKind.ControlPlane, actorId: context.actor.id },
|
|
136
136
|
}, context);
|
|
137
|
-
|
|
137
|
+
const dispatch = root.orchestration
|
|
138
|
+
.applyConversationCommand;
|
|
139
|
+
const applied = dispatch === undefined
|
|
140
|
+
? false
|
|
141
|
+
: await dispatch.call(root.orchestration, id, {
|
|
142
|
+
body: command.body,
|
|
143
|
+
actorId: context.actor.id,
|
|
144
|
+
capabilities: [ConversationSurfaceCapability.Operator],
|
|
145
|
+
authorized: true,
|
|
146
|
+
}, context);
|
|
147
|
+
if (!applied)
|
|
148
|
+
await resumeAgentStages(root, id, context);
|
|
138
149
|
return accepted(command.idempotencyKey, now());
|
|
139
150
|
},
|
|
140
151
|
}
|
|
@@ -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.
|
|
127
|
+
export const wakeVersion = "v0.4.16";
|
|
128
128
|
/** The registry-installable version used by packaged sandbox images. */
|
|
129
129
|
export const wakePackageVersion = packageVersion(defaultRepoRoot) ?? wakeVersion;
|
|
@@ -2,12 +2,25 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const integrationsConfigSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodObject<{
|
|
3
3
|
provider: z.ZodOptional<z.ZodString>;
|
|
4
4
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
conversation: z.ZodOptional<z.ZodObject<{
|
|
6
|
+
capabilities: z.ZodReadonly<z.ZodArray<z.ZodEnum<{
|
|
7
|
+
"review-surface": "review-surface";
|
|
8
|
+
"chat-surface": "chat-surface";
|
|
9
|
+
"operator-surface": "operator-surface";
|
|
10
|
+
}>>>;
|
|
11
|
+
}, z.core.$strict>>;
|
|
5
12
|
}, z.core.$loose>, z.ZodTransform<{
|
|
6
13
|
enabled: boolean;
|
|
7
14
|
provider?: string | undefined;
|
|
15
|
+
conversation?: {
|
|
16
|
+
capabilities: readonly ("review-surface" | "chat-surface" | "operator-surface")[];
|
|
17
|
+
} | undefined;
|
|
8
18
|
}, {
|
|
9
19
|
[x: string]: unknown;
|
|
10
20
|
enabled: boolean;
|
|
11
21
|
provider?: string | undefined;
|
|
22
|
+
conversation?: {
|
|
23
|
+
capabilities: readonly ("review-surface" | "chat-surface" | "operator-surface")[];
|
|
24
|
+
} | undefined;
|
|
12
25
|
}>>>>;
|
|
13
26
|
export type IntegrationsConfig = z.infer<typeof integrationsConfigSchema>;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { surfaceCapabilitySchema } from '../../orchestration/index.js';
|
|
3
|
+
const conversationConfigSchema = z
|
|
4
|
+
.object({ capabilities: z.array(surfaceCapabilitySchema).readonly() })
|
|
5
|
+
.strict();
|
|
2
6
|
const providerEntrySchema = z
|
|
3
7
|
.looseObject({
|
|
4
8
|
provider: z
|
|
@@ -7,6 +11,9 @@ const providerEntrySchema = z
|
|
|
7
11
|
.regex(/^[a-z][a-z0-9-]*$/)
|
|
8
12
|
.optional(),
|
|
9
13
|
enabled: z.boolean().default(true),
|
|
14
|
+
// This is adapter-instance policy. It is intentionally outside provider-specific
|
|
15
|
+
// configuration so two instances of one provider can have different authority.
|
|
16
|
+
conversation: conversationConfigSchema.optional(),
|
|
10
17
|
})
|
|
11
18
|
.transform((value) => ({ ...value, enabled: value.enabled ?? true }));
|
|
12
19
|
export const integrationsConfigSchema = z
|
|
@@ -3,7 +3,7 @@ import type { PullRequestService } from '../../activities/index.js';
|
|
|
3
3
|
import type { ConversationService } from '../../conversations/index.js';
|
|
4
4
|
import type { RunRepository } from '../../execution/index.js';
|
|
5
5
|
import type { Clock, IdGenerator } from '../../kernel/index.js';
|
|
6
|
-
import type { OrchestrationService, WorkflowCandidate, WorkflowName } from '../../orchestration/index.js';
|
|
6
|
+
import type { OrchestrationService, SurfaceCapability, WorkflowCandidate, WorkflowName } from '../../orchestration/index.js';
|
|
7
7
|
import type { ExternalResourceKey, ResourceCapability, ResourceKind, ResourceLookup, ResourceService } from '../../resources/index.js';
|
|
8
8
|
import type { WorkItemId, WorkItemView, WorkService } from '../../work/index.js';
|
|
9
9
|
import type { ExternalDeliveryAdapter } from '../delivery/contracts/config.js';
|
|
@@ -78,6 +78,8 @@ export type ComposedProviderInstance = Omit<ProviderInstance, 'provider'>;
|
|
|
78
78
|
export interface ProviderDefinition<Config = unknown> {
|
|
79
79
|
readonly provider: string;
|
|
80
80
|
readonly eventTypes: readonly string[];
|
|
81
|
+
/** Conservative default authority for each instance of this provider type. */
|
|
82
|
+
readonly defaultConversationCapabilities?: readonly SurfaceCapability[];
|
|
81
83
|
/** Provider-owned periodic reconciliation, invoked in the tick react phase. */
|
|
82
84
|
readonly maintenance?: {
|
|
83
85
|
readonly runOnce: () => Promise<void>;
|
|
@@ -86,6 +88,7 @@ export interface ProviderDefinition<Config = unknown> {
|
|
|
86
88
|
create(input: {
|
|
87
89
|
readonly adapter: AdapterId;
|
|
88
90
|
readonly config: Config;
|
|
91
|
+
readonly conversationCapabilities?: readonly SurfaceCapability[];
|
|
89
92
|
readonly services?: ProviderServices;
|
|
90
93
|
}): ComposedProviderInstance;
|
|
91
94
|
}
|
|
@@ -18,10 +18,12 @@ export class ProviderRegistry {
|
|
|
18
18
|
throw new Error(`Provider ${provider} is not registered`);
|
|
19
19
|
const adapter = adapterId(name);
|
|
20
20
|
try {
|
|
21
|
+
const { conversation: _conversation, provider: _provider, ...providerConfig } = entry;
|
|
21
22
|
instances.push({
|
|
22
23
|
...definition.create({
|
|
23
24
|
adapter,
|
|
24
|
-
config: definition.parseConfig(
|
|
25
|
+
config: definition.parseConfig(providerConfig),
|
|
26
|
+
conversationCapabilities: entry.conversation?.capabilities ?? definition.defaultConversationCapabilities ?? [],
|
|
25
27
|
...(services === undefined ? {} : { services }),
|
|
26
28
|
}),
|
|
27
29
|
provider,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReviewActorKind } from '../../../activities/index.js';
|
|
2
2
|
import { GitHubBuiltInCommand } from '../contracts/vocabulary.js';
|
|
3
3
|
export declare function isHumanNonWakeReply(actorKind: ReviewActorKind, body: string): boolean;
|
|
4
|
-
type IssueCommand = typeof GitHubBuiltInCommand.Approved | typeof GitHubBuiltInCommand.Changes | typeof GitHubBuiltInCommand.Retry | typeof GitHubBuiltInCommand.Restart | typeof GitHubBuiltInCommand.Extend;
|
|
4
|
+
type IssueCommand = typeof GitHubBuiltInCommand.Approved | typeof GitHubBuiltInCommand.Accepted | typeof GitHubBuiltInCommand.Changes | typeof GitHubBuiltInCommand.Retry | typeof GitHubBuiltInCommand.Restart | typeof GitHubBuiltInCommand.Extend;
|
|
5
5
|
export declare function recognizedCommand(body: string): IssueCommand | null;
|
|
6
6
|
export declare function isPlainReply(body: string): boolean;
|
|
7
7
|
export declare function shouldResumeBlockedStage(command: IssueCommand | null, plainReply: boolean): boolean;
|
|
@@ -16,13 +16,15 @@ export function recognizedCommand(body) {
|
|
|
16
16
|
const line = commandLine(body);
|
|
17
17
|
if (matchesCommand(line, GitHubBuiltInCommand.Approved))
|
|
18
18
|
return GitHubBuiltInCommand.Approved;
|
|
19
|
+
if (matchesCommand(line, GitHubBuiltInCommand.Accepted))
|
|
20
|
+
return GitHubBuiltInCommand.Accepted;
|
|
19
21
|
if (matchesCommand(line, GitHubBuiltInCommand.Changes))
|
|
20
22
|
return GitHubBuiltInCommand.Changes;
|
|
21
23
|
if (matchesCommand(line, GitHubBuiltInCommand.Retry))
|
|
22
24
|
return GitHubBuiltInCommand.Retry;
|
|
23
25
|
if (matchesCommand(line, GitHubBuiltInCommand.Restart))
|
|
24
26
|
return GitHubBuiltInCommand.Restart;
|
|
25
|
-
if (line
|
|
27
|
+
if (matchesCommand(line, GitHubBuiltInCommand.Extend))
|
|
26
28
|
return GitHubBuiltInCommand.Extend;
|
|
27
29
|
return null;
|
|
28
30
|
}
|
|
@@ -17,5 +17,7 @@ export declare function applyReviewSignal(input: {
|
|
|
17
17
|
readonly ids: IdGenerator;
|
|
18
18
|
readonly adapter: AdapterId;
|
|
19
19
|
readonly orchestration: OrchestrationService | undefined;
|
|
20
|
+
/** The shared conversation dispatcher owns slash commands in the normal inbound path. */
|
|
21
|
+
readonly applyIssueCommands?: boolean;
|
|
20
22
|
}): Promise<void>;
|
|
21
23
|
export {};
|
|
@@ -8,12 +8,20 @@ import { commandContext } from './inbound-context.js';
|
|
|
8
8
|
import { ignoreIneligibleOperatorRetry } from './operator-retry-command.js';
|
|
9
9
|
import { translateGitHubReviewCommand } from './review-command-translator.js';
|
|
10
10
|
export async function applyReviewSignal(input) {
|
|
11
|
-
const { event, journal, resources, work, lookup, adapter, orchestration } = input;
|
|
11
|
+
const { event, journal, resources, work, lookup, adapter, orchestration, applyIssueCommands } = input;
|
|
12
12
|
if (journal === undefined || resources === undefined || work === undefined)
|
|
13
13
|
return;
|
|
14
14
|
const payload = event.event.payload;
|
|
15
15
|
if (payload.reviewKind === 'issue') {
|
|
16
|
-
await applyIssueReviewSignal({
|
|
16
|
+
await applyIssueReviewSignal({
|
|
17
|
+
event,
|
|
18
|
+
resources,
|
|
19
|
+
work,
|
|
20
|
+
lookup,
|
|
21
|
+
orchestration,
|
|
22
|
+
adapter,
|
|
23
|
+
...(applyIssueCommands === undefined ? {} : { applyIssueCommands }),
|
|
24
|
+
});
|
|
17
25
|
return;
|
|
18
26
|
}
|
|
19
27
|
await applyFormalReviewSignal({ ...input, journal, resources, work });
|
|
@@ -62,7 +70,7 @@ async function applyFormalReviewSignal(input) {
|
|
|
62
70
|
});
|
|
63
71
|
}
|
|
64
72
|
async function applyIssueReviewSignal(input) {
|
|
65
|
-
const { event, resources, work, lookup, orchestration, adapter } = input;
|
|
73
|
+
const { event, resources, work, lookup, orchestration, adapter, applyIssueCommands } = input;
|
|
66
74
|
if (!isHumanNonWakeReply(event.event.payload.actor.kind, event.event.payload.body))
|
|
67
75
|
return;
|
|
68
76
|
if (resources === undefined || lookup === undefined || orchestration === undefined)
|
|
@@ -76,7 +84,11 @@ async function applyIssueReviewSignal(input) {
|
|
|
76
84
|
const resource = await resources.get(resourceIdValue);
|
|
77
85
|
const command = recognizedCommand(event.event.payload.body);
|
|
78
86
|
const plainReply = isPlainReply(event.event.payload.body);
|
|
79
|
-
|
|
87
|
+
// Direct compatibility callers retain the legacy handler; the normal inbound
|
|
88
|
+
// path records first and has the shared dispatcher handle the command exactly once.
|
|
89
|
+
if (command !== null) {
|
|
90
|
+
if (applyIssueCommands === false)
|
|
91
|
+
return;
|
|
80
92
|
return applyIssueCommand({
|
|
81
93
|
event,
|
|
82
94
|
command,
|
|
@@ -88,6 +100,7 @@ async function applyIssueReviewSignal(input) {
|
|
|
88
100
|
adapter,
|
|
89
101
|
resourceId: resourceIdValue,
|
|
90
102
|
});
|
|
103
|
+
}
|
|
91
104
|
if (resource?.kind === BuiltInResourceKind.PullRequest) {
|
|
92
105
|
await applyWorkflowSignal({
|
|
93
106
|
event,
|
|
@@ -3,7 +3,7 @@ import { type ObservePullRequest, type PullRequestService } from '../../../activ
|
|
|
3
3
|
import { type ConversationService } from '../../../conversations/index.js';
|
|
4
4
|
import type { RunRepository } from '../../../execution/index.js';
|
|
5
5
|
import { type IdGenerator } from '../../../kernel/index.js';
|
|
6
|
-
import type { OrchestrationService } from '../../../orchestration/index.js';
|
|
6
|
+
import type { ConversationSurfaceCapability, OrchestrationService } from '../../../orchestration/index.js';
|
|
7
7
|
import type { ResourceLookup, ResourceService } from '../../../resources/index.js';
|
|
8
8
|
import { type ResourceId } from '../../../resources/index.js';
|
|
9
9
|
import type { WorkService } from '../../../work/index.js';
|
|
@@ -44,6 +44,7 @@ interface InboundTranslatorDependencies {
|
|
|
44
44
|
readonly intake?: readonly GitHubIntakeRuleConfig[];
|
|
45
45
|
readonly conclusion?: WorkConclusion;
|
|
46
46
|
readonly conversations?: ConversationService;
|
|
47
|
+
readonly conversationCapabilities?: readonly ConversationSurfaceCapability[];
|
|
47
48
|
}
|
|
48
49
|
export declare class InboundTranslator {
|
|
49
50
|
private readonly journal?;
|
|
@@ -64,6 +65,7 @@ export declare class InboundTranslator {
|
|
|
64
65
|
private readonly intake;
|
|
65
66
|
private readonly conclusion;
|
|
66
67
|
private readonly conversations;
|
|
68
|
+
private readonly conversationCapabilities;
|
|
67
69
|
private conversationRecordRecoveryPending;
|
|
68
70
|
private reconcileOnce;
|
|
69
71
|
private selectInboundEvent;
|
|
@@ -73,6 +75,7 @@ export declare class InboundTranslator {
|
|
|
73
75
|
private recordConversationDeferred;
|
|
74
76
|
private appendConversationRecordFact;
|
|
75
77
|
private recordConversationEntry;
|
|
78
|
+
private applyConversationCommand;
|
|
76
79
|
private failureRecorded;
|
|
77
80
|
private retryRecorded;
|
|
78
81
|
private recordTranslationRecovery;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
import { correlationId, defineEventProcessor, EventActorKind, EventProcessorCategory, EventProcessorReplayPolicy, EventSourceKind, WrongExpectedSequenceError, } from '@atolis-hq/eventing';
|
|
3
|
-
import { ActivityEventType, createPullRequestService, selectActivityEvent, } from '../../../activities/index.js';
|
|
3
|
+
import { ActivityEventType, createPullRequestService, isReviewAuthorized, ReviewerAuthorizationSource, selectActivityEvent, } from '../../../activities/index.js';
|
|
4
4
|
import { conversationIdForWorkItem, ConversationOriginKind, } from '../../../conversations/index.js';
|
|
5
5
|
import { UlidIdGenerator } from '../../../kernel/index.js';
|
|
6
6
|
import { BuiltInResourceCapability, BuiltInResourceKind, ResourceCorrelationRole, ResourceEventType, resourceId, resourceStream, selectResourceEvent, } from '../../../resources/index.js';
|
|
@@ -12,7 +12,7 @@ import { deliveryStream, integrationStream } from '../../contracts/streams.js';
|
|
|
12
12
|
import { DeliveryEventType, selectDeliveryEvent } from '../../delivery/contracts/events.js';
|
|
13
13
|
import { createGitHubEventData } from '../contracts/event-factory.js';
|
|
14
14
|
import { GitHubEventType, selectGitHubAdapterEvent } from '../contracts/events.js';
|
|
15
|
-
import { GitHubAdapter } from '../contracts/vocabulary.js';
|
|
15
|
+
import { GitHubAdapter, UnknownGitHubIdentity } from '../contracts/vocabulary.js';
|
|
16
16
|
import { commandContext } from './inbound-context.js';
|
|
17
17
|
import { applyReviewSignal } from './inbound-review-signals.js';
|
|
18
18
|
import { applyWatchGateVerdictSignal } from './inbound-watch-gate-signals.js';
|
|
@@ -62,6 +62,7 @@ export class InboundTranslator {
|
|
|
62
62
|
this.intake = gitHubIntakeRules(dependencies.intake ?? []);
|
|
63
63
|
this.conclusion = dependencies.conclusion;
|
|
64
64
|
this.conversations = dependencies.conversations;
|
|
65
|
+
this.conversationCapabilities = dependencies.conversationCapabilities ?? [];
|
|
65
66
|
this.processor = defineEventProcessor({
|
|
66
67
|
consumer: `reactor:integration.${this.adapter}.inbound`,
|
|
67
68
|
name: `integration.${this.adapter}.inbound`,
|
|
@@ -83,6 +84,7 @@ export class InboundTranslator {
|
|
|
83
84
|
intake;
|
|
84
85
|
conclusion;
|
|
85
86
|
conversations;
|
|
87
|
+
conversationCapabilities;
|
|
86
88
|
conversationRecordRecoveryPending;
|
|
87
89
|
async reconcileOnce() {
|
|
88
90
|
if (this.journal === undefined || this.work === undefined || this.resources === undefined) {
|
|
@@ -119,6 +121,7 @@ export class InboundTranslator {
|
|
|
119
121
|
ids: this.ids,
|
|
120
122
|
adapter: this.adapter,
|
|
121
123
|
orchestration: this.orchestration,
|
|
124
|
+
applyIssueCommands: false,
|
|
122
125
|
});
|
|
123
126
|
await applyWatchGateVerdictSignal({
|
|
124
127
|
event: owned,
|
|
@@ -223,7 +226,7 @@ export class InboundTranslator {
|
|
|
223
226
|
entry.origin.messageId === externalId);
|
|
224
227
|
if (existing?.entries.some((entry) => entry.representations.some((representation) => representation.resourceId === resource.resourceId &&
|
|
225
228
|
representation.externalId === externalId)))
|
|
226
|
-
return;
|
|
229
|
+
return this.applyConversationCommand(correlation.workItemId, event);
|
|
227
230
|
if (priorEntry !== undefined) {
|
|
228
231
|
if (priorEntry.body !== event.event.payload.body)
|
|
229
232
|
await this.conversations.revise({
|
|
@@ -231,7 +234,7 @@ export class InboundTranslator {
|
|
|
231
234
|
entryId: priorEntry.entryId,
|
|
232
235
|
body: event.event.payload.body,
|
|
233
236
|
}, commandContext(event));
|
|
234
|
-
return;
|
|
237
|
+
return this.applyConversationCommand(correlation.workItemId, event);
|
|
235
238
|
}
|
|
236
239
|
await this.conversations.record({
|
|
237
240
|
conversationId,
|
|
@@ -250,6 +253,18 @@ export class InboundTranslator {
|
|
|
250
253
|
: { location: event.event.payload.location }),
|
|
251
254
|
},
|
|
252
255
|
}, 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));
|
|
253
268
|
}
|
|
254
269
|
async failureRecorded(sourceEventId) {
|
|
255
270
|
return (await this.journal.readStream(integrationStream(this.adapter))).some((event) => {
|
|
@@ -708,6 +723,15 @@ export class InboundTranslator {
|
|
|
708
723
|
}
|
|
709
724
|
}
|
|
710
725
|
}
|
|
726
|
+
function isAuthorizedConversationActor(event) {
|
|
727
|
+
const { actor, authorization, resourceAuthorId } = event.event.payload;
|
|
728
|
+
return isReviewAuthorized({
|
|
729
|
+
actorId: actor.id,
|
|
730
|
+
actorKind: actor.kind,
|
|
731
|
+
resourceAuthorId: resourceAuthorId ?? UnknownGitHubIdentity,
|
|
732
|
+
authorization: authorization ?? { source: ReviewerAuthorizationSource.None },
|
|
733
|
+
});
|
|
734
|
+
}
|
|
711
735
|
function observedCommentExternalId(event) {
|
|
712
736
|
const id = event.event.payload.raw.id;
|
|
713
737
|
return typeof id === 'string' || typeof id === 'number' ? String(id) : event.event.eventId;
|
|
@@ -31,6 +31,8 @@ export interface ExternalWorkObservedPayload {
|
|
|
31
31
|
readonly id: string;
|
|
32
32
|
readonly kind: typeof ReviewActorKind.Human | typeof ReviewActorKind.Bot;
|
|
33
33
|
};
|
|
34
|
+
/** Issue or pull-request author, used to reject self-approval. */
|
|
35
|
+
readonly resourceAuthorId?: string | undefined;
|
|
34
36
|
readonly labels?: readonly string[] | undefined;
|
|
35
37
|
readonly assignees?: readonly string[] | undefined;
|
|
36
38
|
readonly changedFiles?: readonly string[] | undefined;
|
|
@@ -71,6 +73,8 @@ interface GitHubIssueCommentObservedPayload {
|
|
|
71
73
|
readonly id: string;
|
|
72
74
|
readonly kind: typeof ReviewActorKind.Human | typeof ReviewActorKind.Bot;
|
|
73
75
|
};
|
|
76
|
+
/** Issue or pull-request author, used to reject self-approval. */
|
|
77
|
+
readonly resourceAuthorId?: string | undefined;
|
|
74
78
|
/** Provider-derived authority for an operator command, when available. */
|
|
75
79
|
readonly authorization?: ReviewerAuthorizationEvidence | undefined;
|
|
76
80
|
readonly raw: Readonly<Record<string, unknown>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReviewerAuthorizationSource, } from '../../../activities/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { conversationCommand } from '../../../orchestration/index.js';
|
|
3
3
|
import { issueCommentObservation } from './issue-source.js';
|
|
4
4
|
import { mergeBatches, reportPartialPollFailure } from './poll-watermark.js';
|
|
5
5
|
import { githubReviewObservation } from './review-source.js';
|
|
@@ -59,7 +59,7 @@ export async function issueCommentEventsFor(context, issues, since) {
|
|
|
59
59
|
return mergeBatches(items);
|
|
60
60
|
}
|
|
61
61
|
async function issueCommentEventsForComment(context, issue, comment) {
|
|
62
|
-
const authorization = await
|
|
62
|
+
const authorization = await commandAuthorization(context, comment);
|
|
63
63
|
const event = issueCommentObservation({
|
|
64
64
|
repository: context.repository,
|
|
65
65
|
issue,
|
|
@@ -69,9 +69,10 @@ async function issueCommentEventsForComment(context, issue, comment) {
|
|
|
69
69
|
});
|
|
70
70
|
return event === null ? [] : [event];
|
|
71
71
|
}
|
|
72
|
-
async function
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
async function commandAuthorization(context, comment) {
|
|
73
|
+
if (comment.body === null ||
|
|
74
|
+
comment.body === undefined ||
|
|
75
|
+
conversationCommand(comment.body) === null)
|
|
75
76
|
return undefined;
|
|
76
77
|
const login = comment.user?.login;
|
|
77
78
|
if (login === undefined || context.client.collaboratorPermission === undefined)
|
|
@@ -11,7 +11,7 @@ export declare function issueObservation(input: {
|
|
|
11
11
|
}>;
|
|
12
12
|
export declare function issueCommentObservation(input: {
|
|
13
13
|
readonly repository: string;
|
|
14
|
-
readonly issue: Pick<GitHubIssuePayload, 'number'>;
|
|
14
|
+
readonly issue: Pick<GitHubIssuePayload, 'number' | 'user'>;
|
|
15
15
|
readonly comment: GitHubIssueCommentPayload;
|
|
16
16
|
readonly authorization?: ReviewerAuthorizationEvidence;
|
|
17
17
|
readonly adapter?: AdapterId;
|
|
@@ -69,6 +69,7 @@ function parseRepository(repository) {
|
|
|
69
69
|
throw new Error(`Invalid GitHub repository: ${repository}`);
|
|
70
70
|
return { owner, repo };
|
|
71
71
|
}
|
|
72
|
+
// eslint-disable-next-line complexity -- provider payload normalization keeps one canonical envelope.
|
|
72
73
|
export function issueCommentObservation(input) {
|
|
73
74
|
const body = input.comment.body?.trim();
|
|
74
75
|
if (body === undefined || body.length === 0)
|
|
@@ -96,6 +97,7 @@ export function issueCommentObservation(input) {
|
|
|
96
97
|
id: input.comment.user?.login ?? UnknownGitHubIdentity,
|
|
97
98
|
kind: input.comment.user?.type === 'Bot' ? ReviewActorKind.Bot : ReviewActorKind.Human,
|
|
98
99
|
},
|
|
100
|
+
resourceAuthorId: input.issue.user?.login ?? UnknownGitHubIdentity,
|
|
99
101
|
...(input.authorization === undefined ? {} : { authorization: input.authorization }),
|
|
100
102
|
raw: { id: input.comment.id },
|
|
101
103
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BuiltInActivityName, PullRequestState } from '../../activities/index.js';
|
|
2
|
+
import { ConversationSurfaceCapability } from '../../orchestration/index.js';
|
|
2
3
|
import { BuiltInResourceCapability, BuiltInResourceKind, ResourceCorrelationRole, resourceId, resourceKind, } from '../../resources/index.js';
|
|
3
4
|
import { ArtifactVerificationResult } from '../contracts/artifact-vocabulary.js';
|
|
4
5
|
import { InboundTranslator } from './application/inbound-translator.js';
|
|
@@ -16,10 +17,11 @@ import { createGitHubSource } from './infrastructure/source.js';
|
|
|
16
17
|
export const gitHubProviderDefinition = {
|
|
17
18
|
provider: 'github',
|
|
18
19
|
eventTypes: Object.values(GitHubEventType),
|
|
20
|
+
defaultConversationCapabilities: [ConversationSurfaceCapability.Review],
|
|
19
21
|
parseConfig(value) {
|
|
20
22
|
return gitHubConfigSchema.parse(value);
|
|
21
23
|
},
|
|
22
|
-
create({ adapter, config, services }) {
|
|
24
|
+
create({ adapter, config, conversationCapabilities = [ConversationSurfaceCapability.Review], services, }) {
|
|
23
25
|
if (services === undefined)
|
|
24
26
|
throw new Error('GitHub provider requires composed services');
|
|
25
27
|
const client = createGitHubClient(config.token ?? resolveGitHubCliToken());
|
|
@@ -38,6 +40,7 @@ export const gitHubProviderDefinition = {
|
|
|
38
40
|
intake: config.intake,
|
|
39
41
|
conclusion: services.conclusion,
|
|
40
42
|
conversations: services.conversations,
|
|
43
|
+
conversationCapabilities,
|
|
41
44
|
});
|
|
42
45
|
return {
|
|
43
46
|
adapter,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CommandContext } from '@atolis-hq/eventing';
|
|
2
|
+
import { ActivityOutcomeKind } from '../../activities/index.js';
|
|
3
|
+
import { type WorkflowInstanceId } from '../contracts/identifiers.js';
|
|
4
|
+
import type { WorkflowInstanceView } from '../contracts/views.js';
|
|
5
|
+
import { ConversationSurfaceCapability } from '../contracts/vocabulary.js';
|
|
6
|
+
export declare const BuiltInConversationCommands: ("/approved" | "/accepted" | "/changes" | "/retry" | "/restart" | "/extend")[];
|
|
7
|
+
export type SurfaceCapability = ConversationSurfaceCapability;
|
|
8
|
+
export type ConversationCommandSurface = {
|
|
9
|
+
readonly capabilities: readonly SurfaceCapability[];
|
|
10
|
+
};
|
|
11
|
+
export declare function conversationCommand(body: string): string | null;
|
|
12
|
+
export declare function applyBuiltInConversationCommand(input: {
|
|
13
|
+
readonly command: string;
|
|
14
|
+
readonly capabilities: readonly SurfaceCapability[];
|
|
15
|
+
readonly workflows: readonly WorkflowInstanceView[];
|
|
16
|
+
readonly context: CommandContext;
|
|
17
|
+
readonly acceptSignal: (id: WorkflowInstanceId, outcome: typeof ActivityOutcomeKind.Done | typeof ActivityOutcomeKind.Rejected) => Promise<unknown>;
|
|
18
|
+
readonly retry: (id: WorkflowInstanceId) => Promise<unknown>;
|
|
19
|
+
readonly restart: (id: WorkflowInstanceId) => Promise<unknown>;
|
|
20
|
+
readonly extend: (id: WorkflowInstanceId) => Promise<unknown>;
|
|
21
|
+
readonly resumeChanges: (id: WorkflowInstanceId) => Promise<unknown>;
|
|
22
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ActivityOutcomeKind } from '../../activities/index.js';
|
|
2
|
+
import { ConversationBuiltInCommand, ConversationSurfaceCapability, } from '../contracts/vocabulary.js';
|
|
3
|
+
import { isGroupBudgetExtensionEligible, isOperatorRetryEligible, selectOperatorRetryTarget, } from '../domain/operator-retry-policy.js';
|
|
4
|
+
export const BuiltInConversationCommands = Object.values(ConversationBuiltInCommand);
|
|
5
|
+
export function conversationCommand(body) {
|
|
6
|
+
const line = body.trim().split('\n', 1)[0]?.trim().toLowerCase() ?? '';
|
|
7
|
+
return /^\/[a-z][a-z0-9-]*(?:\s.*)?$/.test(line) ? line.split(/\s/, 1)[0] : null;
|
|
8
|
+
}
|
|
9
|
+
// The command matrix is intentionally explicit so future built-ins cannot silently inherit power.
|
|
10
|
+
// eslint-disable-next-line complexity
|
|
11
|
+
export async function applyBuiltInConversationCommand(input) {
|
|
12
|
+
if (!BuiltInConversationCommands.includes(input.command))
|
|
13
|
+
return false;
|
|
14
|
+
if (!input.capabilities.includes(ConversationSurfaceCapability.Review) &&
|
|
15
|
+
!input.capabilities.includes(ConversationSurfaceCapability.Operator))
|
|
16
|
+
return false;
|
|
17
|
+
if (input.command === ConversationBuiltInCommand.Approved ||
|
|
18
|
+
input.command === ConversationBuiltInCommand.Accepted ||
|
|
19
|
+
input.command === ConversationBuiltInCommand.Changes) {
|
|
20
|
+
const outcome = input.command === ConversationBuiltInCommand.Changes
|
|
21
|
+
? ActivityOutcomeKind.Rejected
|
|
22
|
+
: ActivityOutcomeKind.Done;
|
|
23
|
+
for (const workflow of input.workflows) {
|
|
24
|
+
if (workflow.waitingFor !== undefined)
|
|
25
|
+
await input.acceptSignal(workflow.workflowInstanceId, outcome);
|
|
26
|
+
else if (input.command === ConversationBuiltInCommand.Changes)
|
|
27
|
+
await input.resumeChanges(workflow.workflowInstanceId);
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (input.command === ConversationBuiltInCommand.Retry) {
|
|
32
|
+
const target = selectOperatorRetryTarget(input.workflows);
|
|
33
|
+
if (target !== undefined)
|
|
34
|
+
await input.retry(target.workflowInstanceId);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const primary = input.workflows.find((workflow) => workflow.parentWorkflowInstanceId === undefined);
|
|
38
|
+
if (primary === undefined)
|
|
39
|
+
return true;
|
|
40
|
+
if (input.command === ConversationBuiltInCommand.Restart && isOperatorRetryEligible(primary))
|
|
41
|
+
await input.restart(primary.workflowInstanceId);
|
|
42
|
+
if (input.command === ConversationBuiltInCommand.Extend &&
|
|
43
|
+
isGroupBudgetExtensionEligible(primary))
|
|
44
|
+
await input.extend(primary.workflowInstanceId);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
@@ -24,6 +24,16 @@ export class OrchestrationRepository {
|
|
|
24
24
|
return { sequence: loaded.sequence, view: loaded.view };
|
|
25
25
|
}
|
|
26
26
|
async append(id, sequence, drafts) {
|
|
27
|
+
// Command and processor recovery may replay a completed append. Event IDs are
|
|
28
|
+
// deterministic from the command causation, so acknowledge that exact intent
|
|
29
|
+
// instead of appending a second copy after a crash between append and receipt.
|
|
30
|
+
const existing = await this.journal.readStream(workflowInstanceStream(workflowInstanceId(id)));
|
|
31
|
+
const existingIds = new Set(existing.map((event) => event.event.eventId));
|
|
32
|
+
if (drafts.every((draft) => existingIds.has(draft.eventId)))
|
|
33
|
+
return existing
|
|
34
|
+
.filter((event) => drafts.some((draft) => draft.eventId === event.event.eventId))
|
|
35
|
+
.map(decodeOrchestrationEvent)
|
|
36
|
+
.filter(isWorkflowEvent);
|
|
27
37
|
const events = await this.journal.appendToStream(workflowInstanceStream(workflowInstanceId(id)), sequence, drafts);
|
|
28
38
|
return events.map(decodeOrchestrationEvent).filter(isWorkflowEvent);
|
|
29
39
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { CommandContext, EventJournal, ProjectionStore } from '@atolis-hq/eventing';
|
|
2
2
|
import type { ActivationId, ActivityOutcome } from '../../activities/index.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type WorkItemId, type WorkService } from '../../work/index.js';
|
|
4
4
|
import type { StartWorkflowInstance } from '../contracts/commands.js';
|
|
5
5
|
import type { CompiledWorkflow, TransitionTarget } from '../contracts/config.js';
|
|
6
6
|
import type { ChildWorkflowRequest, OrchestrationSignal, SignalExpectation, SupplementalActivityRequest } from '../contracts/events.js';
|
|
7
7
|
import { type SignalName, type WorkflowInstanceId } from '../contracts/identifiers.js';
|
|
8
8
|
import type { WorkflowInstanceView } from '../contracts/views.js';
|
|
9
9
|
import { AdvanceWorkflow } from './advance-workflow.js';
|
|
10
|
+
import { type SurfaceCapability } from './conversation-command.js';
|
|
10
11
|
export declare class OrchestrationService {
|
|
11
12
|
private readonly startWorkflow;
|
|
12
13
|
private readonly acceptActivityOutcome;
|
|
@@ -19,6 +20,7 @@ export declare class OrchestrationService {
|
|
|
19
20
|
private readonly claims;
|
|
20
21
|
private readonly journal;
|
|
21
22
|
private readonly definitions;
|
|
23
|
+
private readonly work;
|
|
22
24
|
constructor(journal: EventJournal, work: WorkService, definitions: Readonly<Record<string, CompiledWorkflow>>, projections?: ProjectionStore);
|
|
23
25
|
start(command: StartWorkflowInstance, context: CommandContext): Promise<WorkflowInstanceView>;
|
|
24
26
|
requestChild(request: ChildWorkflowRequest & {
|
|
@@ -35,6 +37,13 @@ export declare class OrchestrationService {
|
|
|
35
37
|
setAcceptSignalOperationCoordinator(coordinator: OperationCoordinator): void;
|
|
36
38
|
setWatchChildCancellation(cancellation: WatchChildCancellation): void;
|
|
37
39
|
requestSupplementalActivity(workflowInstanceId: WorkflowInstanceId, request: SupplementalActivityRequest, context: CommandContext): Promise<WorkflowInstanceView>;
|
|
40
|
+
applyConversationCommand(workItemId: WorkItemId, input: {
|
|
41
|
+
readonly body: string;
|
|
42
|
+
readonly actorId: string;
|
|
43
|
+
readonly capabilities: readonly SurfaceCapability[];
|
|
44
|
+
/** The adapter authenticated and authorized the actor for command control. */
|
|
45
|
+
readonly authorized: boolean;
|
|
46
|
+
}, context: CommandContext): Promise<boolean>;
|
|
38
47
|
markActivationStarted(workflowInstanceId: WorkflowInstanceId, activationId: ActivationId, context: CommandContext): Promise<WorkflowInstanceView | null>;
|
|
39
48
|
validateActivationDispatch(workflowInstanceId: WorkflowInstanceId, context: CommandContext): Promise<boolean>;
|
|
40
49
|
block(workflowInstanceId: WorkflowInstanceId, reason: string, context: CommandContext): Promise<WorkflowInstanceView | null>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { WorkStatus } from '../../work/index.js';
|
|
1
2
|
import { selectOrchestrationEvent } from '../contracts/event-decoder.js';
|
|
2
3
|
import { OrchestrationEventType } from '../contracts/events.js';
|
|
3
|
-
import { workflowInstanceId as toWorkflowInstanceId, } from '../contracts/identifiers.js';
|
|
4
|
+
import { commandName, workflowInstanceId as toWorkflowInstanceId, } from '../contracts/identifiers.js';
|
|
4
5
|
import { childOrchestrationGroupStream, workflowInstanceStream } from '../contracts/streams.js';
|
|
5
6
|
import { ApprovalAuthorityKind } from '../contracts/vocabulary.js';
|
|
6
7
|
import { isGroupBudgetExtensionEligible } from '../domain/operator-retry-policy.js';
|
|
7
8
|
import { AcceptActivityOutcome } from './accept-activity-outcome.js';
|
|
8
9
|
import { AcceptSignal } from './accept-signal.js';
|
|
9
10
|
import { AdvanceWorkflow } from './advance-workflow.js';
|
|
11
|
+
import { applyBuiltInConversationCommand, conversationCommand, } from './conversation-command.js';
|
|
10
12
|
import { CoordinationClaims } from './coordination-claims.js';
|
|
11
13
|
import { GroupBudgetRecorder } from './group-budget-recorder.js';
|
|
12
14
|
import { workflowsByWorkItemProjection } from './orchestration-projection.js';
|
|
@@ -27,9 +29,11 @@ export class OrchestrationService {
|
|
|
27
29
|
claims;
|
|
28
30
|
journal;
|
|
29
31
|
definitions;
|
|
32
|
+
work;
|
|
30
33
|
constructor(journal, work, definitions, projections) {
|
|
31
34
|
this.projections = projections;
|
|
32
35
|
this.journal = journal;
|
|
36
|
+
this.work = work;
|
|
33
37
|
const repository = new OrchestrationRepository(journal);
|
|
34
38
|
this.claims = new CoordinationClaims(journal);
|
|
35
39
|
this.definitions = new WorkflowDefinitionRegistry(journal, projections, definitions);
|
|
@@ -66,6 +70,49 @@ export class OrchestrationService {
|
|
|
66
70
|
requestSupplementalActivity(workflowInstanceId, request, context) {
|
|
67
71
|
return this.advanceWorkflow.requestSupplementalActivity(workflowInstanceId, request, context);
|
|
68
72
|
}
|
|
73
|
+
async applyConversationCommand(workItemId, input, context) {
|
|
74
|
+
const command = conversationCommand(input.body);
|
|
75
|
+
if (command === null)
|
|
76
|
+
return false;
|
|
77
|
+
if (!input.authorized)
|
|
78
|
+
return false;
|
|
79
|
+
const work = await this.work.get(workItemId);
|
|
80
|
+
if (work === null || work.deleted || work.frozen || work.state !== WorkStatus.Open)
|
|
81
|
+
return false;
|
|
82
|
+
// A command is causally adjacent to its conversation entry; projections may lag
|
|
83
|
+
// the journal, so command application must read the authoritative workflow state.
|
|
84
|
+
const workflows = (await this.advanceWorkflow.listAll()).filter((workflow) => workflow.workItemId === workItemId);
|
|
85
|
+
// Configured commands deliberately win over built-ins, even when their spelling collides.
|
|
86
|
+
let supplemental = false;
|
|
87
|
+
for (const workflow of workflows) {
|
|
88
|
+
const definition = await this.definitions.resolve(workflow);
|
|
89
|
+
if (definition.commands[command] === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
supplemental = true;
|
|
92
|
+
await this.requestSupplementalActivity(workflow.workflowInstanceId, { command: commandName(command) }, context);
|
|
93
|
+
}
|
|
94
|
+
if (supplemental)
|
|
95
|
+
return true;
|
|
96
|
+
return applyBuiltInConversationCommand({
|
|
97
|
+
command,
|
|
98
|
+
capabilities: input.capabilities,
|
|
99
|
+
workflows,
|
|
100
|
+
context,
|
|
101
|
+
acceptSignal: (id, outcome) => this.acceptSignal(id, {
|
|
102
|
+
kind: (workflows.find((workflow) => workflow.workflowInstanceId === id)?.waitingFor
|
|
103
|
+
?.signalKind ?? 'approved'),
|
|
104
|
+
outcome,
|
|
105
|
+
actorId: input.actorId,
|
|
106
|
+
actorDecision: { authorized: true, evidenceId: context.commandId },
|
|
107
|
+
providerEventId: context.commandId,
|
|
108
|
+
authority: { kind: ApprovalAuthorityKind.Human },
|
|
109
|
+
}, context),
|
|
110
|
+
retry: (id) => this.retryBlockedFailedStage(id, context),
|
|
111
|
+
restart: (id) => this.restartBlockedFailedStage(id, context),
|
|
112
|
+
extend: (id) => this.extendBlockedGroupBudget(id, { kind: ApprovalAuthorityKind.Human }, context),
|
|
113
|
+
resumeChanges: (id) => this.resumeBlockedStageForChanges(id, context),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
69
116
|
markActivationStarted(workflowInstanceId, activationId, context) {
|
|
70
117
|
return this.advanceWorkflow.markActivationStarted(workflowInstanceId, activationId, context);
|
|
71
118
|
}
|
|
@@ -37,6 +37,11 @@ declare const resourceTransitionConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
37
37
|
}, z.core.$strict>;
|
|
38
38
|
then: z.ZodOptional<z.ZodString>;
|
|
39
39
|
}, z.core.$strict>]>;
|
|
40
|
+
export declare const surfaceCapabilitySchema: z.ZodEnum<{
|
|
41
|
+
"review-surface": "review-surface";
|
|
42
|
+
"chat-surface": "chat-surface";
|
|
43
|
+
"operator-surface": "operator-surface";
|
|
44
|
+
}>;
|
|
40
45
|
export declare const watchConfigSchema: z.ZodObject<{
|
|
41
46
|
id: z.ZodString;
|
|
42
47
|
while: z.ZodObject<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ApprovalAuthorityKind, WorkflowStatus } from './vocabulary.js';
|
|
2
|
+
import { ApprovalAuthorityKind, ConversationSurfaceCapability, WorkflowStatus, } from './vocabulary.js';
|
|
3
3
|
import { ActivityEventType, PullRequestCheckState, PullRequestState, } from '../../activities/index.js';
|
|
4
4
|
import { WorkspaceMode } from '../../execution/index.js';
|
|
5
5
|
import { MatchMode } from '../../kernel/index.js';
|
|
@@ -54,6 +54,11 @@ const resourceTransitionConfigSchema = z.union([
|
|
|
54
54
|
.strict(),
|
|
55
55
|
]);
|
|
56
56
|
const commandName = z.string().regex(/^\/[a-z][a-z0-9-]*$/);
|
|
57
|
+
export const surfaceCapabilitySchema = z.enum([
|
|
58
|
+
ConversationSurfaceCapability.Review,
|
|
59
|
+
ConversationSurfaceCapability.Chat,
|
|
60
|
+
ConversationSurfaceCapability.Operator,
|
|
61
|
+
]);
|
|
57
62
|
const canonicalEventName = z.string().regex(/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/);
|
|
58
63
|
const watchStatus = z.enum([WorkflowStatus.Active, WorkflowStatus.Waiting, WorkflowStatus.Blocked]);
|
|
59
64
|
const failingChecksWatchPredicateSchema = z
|
|
@@ -32,3 +32,18 @@ export declare const ApprovalAuthorityKind: {
|
|
|
32
32
|
readonly Watch: "watch";
|
|
33
33
|
};
|
|
34
34
|
export type ApprovalAuthorityKind = ValueOf<typeof ApprovalAuthorityKind>;
|
|
35
|
+
export declare const ConversationBuiltInCommand: {
|
|
36
|
+
readonly Approved: "/approved";
|
|
37
|
+
readonly Accepted: "/accepted";
|
|
38
|
+
readonly Changes: "/changes";
|
|
39
|
+
readonly Retry: "/retry";
|
|
40
|
+
readonly Restart: "/restart";
|
|
41
|
+
readonly Extend: "/extend";
|
|
42
|
+
};
|
|
43
|
+
export type ConversationBuiltInCommand = ValueOf<typeof ConversationBuiltInCommand>;
|
|
44
|
+
export declare const ConversationSurfaceCapability: {
|
|
45
|
+
readonly Review: "review-surface";
|
|
46
|
+
readonly Chat: "chat-surface";
|
|
47
|
+
readonly Operator: "operator-surface";
|
|
48
|
+
};
|
|
49
|
+
export type ConversationSurfaceCapability = ValueOf<typeof ConversationSurfaceCapability>;
|
|
@@ -29,3 +29,16 @@ export const ApprovalAuthorityKind = defineClosedVocabulary({
|
|
|
29
29
|
Auto: 'auto',
|
|
30
30
|
Watch: 'watch',
|
|
31
31
|
});
|
|
32
|
+
export const ConversationBuiltInCommand = defineClosedVocabulary({
|
|
33
|
+
Approved: '/approved',
|
|
34
|
+
Accepted: '/accepted',
|
|
35
|
+
Changes: '/changes',
|
|
36
|
+
Retry: '/retry',
|
|
37
|
+
Restart: '/restart',
|
|
38
|
+
Extend: '/extend',
|
|
39
|
+
});
|
|
40
|
+
export const ConversationSurfaceCapability = defineClosedVocabulary({
|
|
41
|
+
Review: 'review-surface',
|
|
42
|
+
Chat: 'chat-surface',
|
|
43
|
+
Operator: 'operator-surface',
|
|
44
|
+
});
|
|
@@ -10,6 +10,7 @@ export * from './application/resource-transition-reactor.js';
|
|
|
10
10
|
export * from './application/signal-reactor.js';
|
|
11
11
|
export * from './application/watch-reactor.js';
|
|
12
12
|
export * from './application/trigger-workflow-instance.js';
|
|
13
|
+
export * from './application/conversation-command.js';
|
|
13
14
|
export * from './application/workflow-definition-registry.js';
|
|
14
15
|
export * from './contracts/activity-outcome.js';
|
|
15
16
|
export * from './contracts/commands.js';
|
|
@@ -10,6 +10,7 @@ export * from './application/resource-transition-reactor.js';
|
|
|
10
10
|
export * from './application/signal-reactor.js';
|
|
11
11
|
export * from './application/watch-reactor.js';
|
|
12
12
|
export * from './application/trigger-workflow-instance.js';
|
|
13
|
+
export * from './application/conversation-command.js';
|
|
13
14
|
export * from './application/workflow-definition-registry.js';
|
|
14
15
|
export * from './contracts/activity-outcome.js';
|
|
15
16
|
export * from './contracts/commands.js';
|