@bizagent/dsh 0.0.0-stage → 1.0.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.
Files changed (181) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/LICENSE +21 -0
  3. package/README.md +61 -2
  4. package/cordis.patch.yml +14 -0
  5. package/docs/README.md +23 -0
  6. package/docs/architecture.md +154 -0
  7. package/docs/concepts.md +192 -0
  8. package/docs/configuration.md +154 -0
  9. package/docs/quickstart.md +103 -0
  10. package/docs/quickstart.zh-CN.md +86 -0
  11. package/docs/tools.md +607 -0
  12. package/docs/user-guide.zh-CN.md +132 -0
  13. package/docs/verification.md +69 -0
  14. package/examples/README.md +64 -0
  15. package/examples/demo-runtime.mjs +71 -0
  16. package/examples/learning-lab.mjs +263 -0
  17. package/examples/scenarios.mjs +45 -0
  18. package/lib/agent-preset.d.ts +20 -0
  19. package/lib/agent-preset.js +46 -0
  20. package/lib/agent-session-address.d.ts +14 -0
  21. package/lib/agent-session-address.js +15 -0
  22. package/lib/agent-session-coordinator.d.ts +24 -0
  23. package/lib/agent-session-coordinator.js +101 -0
  24. package/lib/cli.d.ts +3 -0
  25. package/lib/cli.js +142 -0
  26. package/lib/client-types/agent-session-address.d.ts +13 -0
  27. package/lib/client-types/client/agent-mentions.d.ts +3 -0
  28. package/lib/client-types/client/api.d.ts +180 -0
  29. package/lib/client-types/client/components.d.ts +22 -0
  30. package/lib/client-types/client/controller.d.ts +23 -0
  31. package/lib/client-types/client/frame-participants.d.ts +5 -0
  32. package/lib/client-types/client/home-name.d.ts +9 -0
  33. package/lib/client-types/client/human-inbox.d.ts +8 -0
  34. package/lib/client-types/client/index.d.ts +13 -0
  35. package/lib/client-types/client/locales.d.ts +796 -0
  36. package/lib/client-types/client/markdown.d.ts +12 -0
  37. package/lib/client-types/client/project-artifact-link.d.ts +10 -0
  38. package/lib/client-types/client/project-outcome-review-inbox.d.ts +13 -0
  39. package/lib/client-types/client/projects.d.ts +11 -0
  40. package/lib/client-types/client/sample-guide.d.ts +9 -0
  41. package/lib/client-types/client/session-resource.d.ts +34 -0
  42. package/lib/client-types/client/session-surfaces.d.ts +24 -0
  43. package/lib/client-types/client/styles.d.ts +1 -0
  44. package/lib/client-types/config.d.ts +55 -0
  45. package/lib/client-types/cron.d.ts +8 -0
  46. package/lib/client-types/domain.d.ts +818 -0
  47. package/lib/client-types/frame-reference.d.ts +4 -0
  48. package/lib/client-types/home-directory.d.ts +5 -0
  49. package/lib/client-types/home-space.d.ts +49 -0
  50. package/lib/client-types/home-store.d.ts +125 -0
  51. package/lib/client-types/inbox-stalled-topic.d.ts +15 -0
  52. package/lib/client-types/inbox-wake-policy.d.ts +18 -0
  53. package/lib/client-types/known-format.d.ts +2 -0
  54. package/lib/client-types/learning-scheduler.d.ts +36 -0
  55. package/lib/client-types/learning-session-runner.d.ts +49 -0
  56. package/lib/client-types/learning-trigger.d.ts +17 -0
  57. package/lib/client-types/learning-usage.d.ts +17 -0
  58. package/lib/client-types/ledger.d.ts +190 -0
  59. package/lib/client-types/local-owner.d.ts +7 -0
  60. package/lib/client-types/message-source.d.ts +9 -0
  61. package/lib/client-types/organization-location.d.ts +19 -0
  62. package/lib/client-types/project-ledger.d.ts +182 -0
  63. package/lib/client-types/project-seat-context.d.ts +17 -0
  64. package/lib/client-types/safe-file.d.ts +23 -0
  65. package/lib/client-types/sample-organization.d.ts +9 -0
  66. package/lib/client-types/service.d.ts +596 -0
  67. package/lib/client-types/shared-knowledge.d.ts +116 -0
  68. package/lib/client-types/ui-contract.d.ts +276 -0
  69. package/lib/client-types/untrusted-text.d.ts +15 -0
  70. package/lib/client-types/work-ledger.d.ts +108 -0
  71. package/lib/client-types/work-output-eval.d.ts +15 -0
  72. package/lib/client-types/worklog-ledger.d.ts +88 -0
  73. package/lib/client-types/worklog-snapshot.d.ts +33 -0
  74. package/lib/client.js +5463 -0
  75. package/lib/config.d.ts +56 -0
  76. package/lib/config.js +115 -0
  77. package/lib/cron.d.ts +9 -0
  78. package/lib/cron.js +127 -0
  79. package/lib/domain.d.ts +819 -0
  80. package/lib/domain.js +662 -0
  81. package/lib/frame-admission.d.ts +6 -0
  82. package/lib/frame-admission.js +39 -0
  83. package/lib/frame-reference.d.ts +5 -0
  84. package/lib/frame-reference.js +12 -0
  85. package/lib/home-archive.d.ts +17 -0
  86. package/lib/home-archive.js +177 -0
  87. package/lib/home-directory.d.ts +6 -0
  88. package/lib/home-directory.js +38 -0
  89. package/lib/home-space.d.ts +50 -0
  90. package/lib/home-space.js +120 -0
  91. package/lib/home-store.d.ts +126 -0
  92. package/lib/home-store.js +837 -0
  93. package/lib/host.d.ts +22 -0
  94. package/lib/host.js +15 -0
  95. package/lib/human-inbox-transport.d.ts +23 -0
  96. package/lib/human-inbox-transport.js +105 -0
  97. package/lib/human-notification-runner.d.ts +83 -0
  98. package/lib/human-notification-runner.js +138 -0
  99. package/lib/human-notification-webhook.d.ts +18 -0
  100. package/lib/human-notification-webhook.js +102 -0
  101. package/lib/human-turn-completion.d.ts +61 -0
  102. package/lib/human-turn-completion.js +157 -0
  103. package/lib/inbox-return-notifier.d.ts +46 -0
  104. package/lib/inbox-return-notifier.js +279 -0
  105. package/lib/inbox-stalled-escalation-runner.d.ts +31 -0
  106. package/lib/inbox-stalled-escalation-runner.js +68 -0
  107. package/lib/inbox-stalled-topic.d.ts +16 -0
  108. package/lib/inbox-stalled-topic.js +44 -0
  109. package/lib/inbox-wake-policy.d.ts +19 -0
  110. package/lib/inbox-wake-policy.js +28 -0
  111. package/lib/inbox-wake-runner.d.ts +45 -0
  112. package/lib/inbox-wake-runner.js +481 -0
  113. package/lib/index.d.ts +29 -0
  114. package/lib/index.js +480 -0
  115. package/lib/known-format.d.ts +3 -0
  116. package/lib/known-format.js +8 -0
  117. package/lib/learning-scheduler.d.ts +37 -0
  118. package/lib/learning-scheduler.js +182 -0
  119. package/lib/learning-session-runner.d.ts +50 -0
  120. package/lib/learning-session-runner.js +435 -0
  121. package/lib/learning-trigger.d.ts +18 -0
  122. package/lib/learning-trigger.js +92 -0
  123. package/lib/learning-usage.d.ts +18 -0
  124. package/lib/learning-usage.js +49 -0
  125. package/lib/ledger.d.ts +191 -0
  126. package/lib/ledger.js +752 -0
  127. package/lib/local-human-host.d.ts +11 -0
  128. package/lib/local-human-host.js +191 -0
  129. package/lib/local-human-token.d.ts +9 -0
  130. package/lib/local-human-token.js +32 -0
  131. package/lib/local-owner.d.ts +8 -0
  132. package/lib/local-owner.js +26 -0
  133. package/lib/message-source.d.ts +10 -0
  134. package/lib/message-source.js +2 -0
  135. package/lib/organization-location.d.ts +20 -0
  136. package/lib/organization-location.js +80 -0
  137. package/lib/organization-workspace.d.ts +22 -0
  138. package/lib/organization-workspace.js +32 -0
  139. package/lib/project-ledger.d.ts +183 -0
  140. package/lib/project-ledger.js +189 -0
  141. package/lib/project-review-transport.d.ts +23 -0
  142. package/lib/project-review-transport.js +155 -0
  143. package/lib/project-seat-context.d.ts +18 -0
  144. package/lib/project-seat-context.js +34 -0
  145. package/lib/proposal-review-transport.d.ts +26 -0
  146. package/lib/proposal-review-transport.js +104 -0
  147. package/lib/route-guard.d.ts +31 -0
  148. package/lib/route-guard.js +80 -0
  149. package/lib/safe-file.d.ts +24 -0
  150. package/lib/safe-file.js +93 -0
  151. package/lib/sample-organization.d.ts +10 -0
  152. package/lib/sample-organization.js +46 -0
  153. package/lib/service.d.ts +597 -0
  154. package/lib/service.js +4076 -0
  155. package/lib/session-surface.d.ts +11 -0
  156. package/lib/session-surface.js +109 -0
  157. package/lib/shared-knowledge-review-transport.d.ts +28 -0
  158. package/lib/shared-knowledge-review-transport.js +85 -0
  159. package/lib/shared-knowledge.d.ts +117 -0
  160. package/lib/shared-knowledge.js +240 -0
  161. package/lib/tools.d.ts +7 -0
  162. package/lib/tools.js +955 -0
  163. package/lib/ui-contract.d.ts +277 -0
  164. package/lib/ui-contract.js +12 -0
  165. package/lib/ui-host.d.ts +24 -0
  166. package/lib/ui-host.js +471 -0
  167. package/lib/untrusted-text.d.ts +16 -0
  168. package/lib/untrusted-text.js +50 -0
  169. package/lib/work-ledger.d.ts +109 -0
  170. package/lib/work-ledger.js +107 -0
  171. package/lib/work-output-eval.d.ts +16 -0
  172. package/lib/work-output-eval.js +58 -0
  173. package/lib/work-runner.d.ts +47 -0
  174. package/lib/work-runner.js +352 -0
  175. package/lib/worklog-ledger.d.ts +89 -0
  176. package/lib/worklog-ledger.js +91 -0
  177. package/lib/worklog-snapshot.d.ts +34 -0
  178. package/lib/worklog-snapshot.js +100 -0
  179. package/lib/writer-guard.d.ts +11 -0
  180. package/lib/writer-guard.js +207 -0
  181. package/package.json +172 -4
package/lib/host.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export { BIZAGENT_PROJECT_REVIEW_API } from './ui-contract.js';
2
+ export { createProjectReviewHttpHandler, registerProjectReviewRoute } from './project-review-transport.js';
3
+ export type { ProjectReviewPrincipal, ProjectReviewRouteOptions } from './project-review-transport.js';
4
+ export { BIZAGENT_PROPOSAL_REVIEW_API, createProposalReviewHttpHandler, registerProposalReviewRoute } from './proposal-review-transport.js';
5
+ export type { ProposalReviewPrincipal, ProposalReviewRouteOptions } from './proposal-review-transport.js';
6
+ export { SHARED_KNOWLEDGE_REVIEW_API, createSharedKnowledgeReviewHttpHandler, registerSharedKnowledgeReviewRoute } from './shared-knowledge-review-transport.js';
7
+ export type { SharedKnowledgeReviewOptions } from './shared-knowledge-review-transport.js';
8
+ export { HUMAN_INBOX_API, createHumanInboxHttpHandler, registerHumanInboxRoute } from './human-inbox-transport.js';
9
+ export type { HumanInboxPrincipal, HumanInboxRouteOptions } from './human-inbox-transport.js';
10
+ export { HumanNotificationRunner } from './human-notification-runner.js';
11
+ export type { HumanNotificationProvider, HumanNotificationRequest, HumanNotificationOutcome, HumanNotificationPass, HumanNotificationRunnerOptions, HumanNotificationPolicyDecision } from './human-notification-runner.js';
12
+ export { HumanNotificationWebhook } from './human-notification-webhook.js';
13
+ export type { HumanNotificationWebhookOptions } from './human-notification-webhook.js';
14
+ export { HumanTurnCompletionNotifier } from './human-turn-completion.js';
15
+ export type { HumanTurnCandidate, HumanTurnDestination, HumanTurnCompletionSource, HumanTurnPersistence, HumanTurnCompletionPass } from './human-turn-completion.js';
16
+ export { InboxStalledEscalationRunner } from './inbox-stalled-escalation-runner.js';
17
+ export type { EscalationOwnerDecision, StalledEscalationPass } from './inbox-stalled-escalation-runner.js';
18
+ export { stalledInboxTopics } from './inbox-stalled-topic.js';
19
+ export type { StalledInboxTopic } from './inbox-stalled-topic.js';
20
+ export { exportHomeArchive, verifyHomeArchive, importHomeArchive } from './home-archive.js';
21
+ export type { HomeArchiveManifest } from './home-archive.js';
22
+ //# sourceMappingURL=host.d.ts.map
package/lib/host.js ADDED
@@ -0,0 +1,15 @@
1
+ // Building blocks for hosts that bring their own user authentication, notification channels,
2
+ // or backup jobs. The local single-user setup does not need them: `@bizagent/dsh/local-human-host`
3
+ // mounts the review and Inbox routes for one person on 127.0.0.1.
4
+ export { BIZAGENT_PROJECT_REVIEW_API } from './ui-contract.js';
5
+ export { createProjectReviewHttpHandler, registerProjectReviewRoute } from './project-review-transport.js';
6
+ export { BIZAGENT_PROPOSAL_REVIEW_API, createProposalReviewHttpHandler, registerProposalReviewRoute } from './proposal-review-transport.js';
7
+ export { SHARED_KNOWLEDGE_REVIEW_API, createSharedKnowledgeReviewHttpHandler, registerSharedKnowledgeReviewRoute } from './shared-knowledge-review-transport.js';
8
+ export { HUMAN_INBOX_API, createHumanInboxHttpHandler, registerHumanInboxRoute } from './human-inbox-transport.js';
9
+ export { HumanNotificationRunner } from './human-notification-runner.js';
10
+ export { HumanNotificationWebhook } from './human-notification-webhook.js';
11
+ export { HumanTurnCompletionNotifier } from './human-turn-completion.js';
12
+ export { InboxStalledEscalationRunner } from './inbox-stalled-escalation-runner.js';
13
+ export { stalledInboxTopics } from './inbox-stalled-topic.js';
14
+ export { exportHomeArchive, verifyHomeArchive, importHomeArchive } from './home-archive.js';
15
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1,23 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { BizAgentService } from './service.js';
3
+ export declare const HUMAN_INBOX_API = "/api/bizagent/human-inbox";
4
+ export interface HumanInboxPrincipal {
5
+ /** Verified by the host's session or bearer credential, never read from request fields. */
6
+ userId: string;
7
+ }
8
+ export interface HumanInboxRouteOptions {
9
+ authenticateUser: (request: IncomingMessage) => HumanInboxPrincipal | null | Promise<HumanInboxPrincipal | null>;
10
+ /** The host checks CSRF protection or a non-cookie credential for every mutation. */
11
+ authorizeMutation: (request: IncomingMessage, principal: HumanInboxPrincipal) => boolean | Promise<boolean>;
12
+ }
13
+ /** Opt-in route; the core plugin cannot infer a human identity from DSH Agent bindings. */
14
+ export declare function createHumanInboxHttpHandler(service: BizAgentService, options: HumanInboxRouteOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
15
+ /** Mount on the local DSH web server behind the loopback Host / same-origin route guard. */
16
+ export declare function registerHumanInboxRoute(webServer: {
17
+ register: (route: {
18
+ kind: 'exact';
19
+ path: string;
20
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
21
+ }) => () => void;
22
+ }, service: BizAgentService, options: HumanInboxRouteOptions): () => void;
23
+ //# sourceMappingURL=human-inbox-transport.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import { guardLocalRoute } from './route-guard.js';
3
+ export const HUMAN_INBOX_API = '/api/bizagent/human-inbox';
4
+ const MAX_BODY_BYTES = 64 * 1024;
5
+ const HumanInboxActionSchema = z.discriminatedUnion('action', [
6
+ z.strictObject({ action: z.literal('handle'), messageId: z.string().min(1).max(200) }),
7
+ z.strictObject({ action: z.literal('reply'), messageId: z.string().min(1).max(200),
8
+ subject: z.string().min(1).max(200), body: z.string().min(1).max(16000),
9
+ idempotencyKey: z.string().min(1).max(128) }),
10
+ z.strictObject({ action: z.literal('notification-preference'), enabled: z.boolean(),
11
+ expectedRevision: z.number().int().nonnegative() }),
12
+ ]);
13
+ /** Opt-in route; the core plugin cannot infer a human identity from DSH Agent bindings. */
14
+ export function createHumanInboxHttpHandler(service, options) {
15
+ return async (req, res) => {
16
+ res.setHeader('Cache-Control', 'no-store');
17
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
18
+ res.setHeader('X-Content-Type-Options', 'nosniff');
19
+ if (req.method !== 'GET' && req.method !== 'POST') {
20
+ res.setHeader('Allow', 'GET, POST');
21
+ reply(res, 405, { error: 'method-not-allowed' });
22
+ return;
23
+ }
24
+ try {
25
+ const principal = await options.authenticateUser(req);
26
+ if (!principal || !principal.userId.trim()) {
27
+ reply(res, 401, { error: 'authentication-required' });
28
+ return;
29
+ }
30
+ if (req.method === 'GET') {
31
+ const url = new URL(req.url ?? '', 'http://bizagent.local');
32
+ const messageId = url.searchParams.get('messageId');
33
+ if (messageId) {
34
+ reply(res, 200, service.readHumanInbox(principal.userId, messageId));
35
+ return;
36
+ }
37
+ const direction = url.searchParams.get('direction');
38
+ const beforeMessageId = url.searchParams.get('beforeMessageId');
39
+ const limit = url.searchParams.get('limit');
40
+ reply(res, 200, service.listHumanInbox(principal.userId, {
41
+ ...(direction !== null ? { direction: direction } : {}),
42
+ ...(beforeMessageId !== null ? { beforeMessageId } : {}),
43
+ ...(limit !== null ? { limit: Number(limit) } : {}),
44
+ pendingOnly: url.searchParams.get('pendingOnly') === 'true',
45
+ }));
46
+ return;
47
+ }
48
+ if (!(await options.authorizeMutation(req, principal))) {
49
+ reply(res, 403, { error: 'mutation-forbidden' });
50
+ return;
51
+ }
52
+ if (!String(req.headers['content-type'] ?? '').toLowerCase().startsWith('application/json')) {
53
+ reply(res, 415, { error: 'content-type' });
54
+ return;
55
+ }
56
+ const action = HumanInboxActionSchema.parse(await readJson(req));
57
+ const result = action.action === 'handle'
58
+ ? await service.handleHumanInbox(principal.userId, action.messageId)
59
+ : action.action === 'notification-preference'
60
+ ? await service.setHumanNotificationPreference(principal.userId, action.enabled, action.expectedRevision)
61
+ : await service.replyHumanInbox(principal.userId, {
62
+ replyTo: action.messageId, subject: action.subject, body: action.body,
63
+ idempotencyKey: action.idempotencyKey,
64
+ });
65
+ reply(res, 200, result);
66
+ }
67
+ catch (error) {
68
+ if (error instanceof z.ZodError || error instanceof RequestError) {
69
+ reply(res, 400, { error: 'invalid-request' });
70
+ }
71
+ else {
72
+ reply(res, 409, { error: 'inbox-operation-rejected' });
73
+ }
74
+ }
75
+ };
76
+ }
77
+ /** Mount on the local DSH web server behind the loopback Host / same-origin route guard. */
78
+ export function registerHumanInboxRoute(webServer, service, options) {
79
+ return webServer.register({ kind: 'exact', path: HUMAN_INBOX_API,
80
+ handler: guardLocalRoute(createHumanInboxHttpHandler(service, options)) });
81
+ }
82
+ class RequestError extends Error {
83
+ }
84
+ async function readJson(req) {
85
+ let total = 0;
86
+ const chunks = [];
87
+ for await (const chunk of req) {
88
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
89
+ total += bytes.length;
90
+ if (total > MAX_BODY_BYTES)
91
+ throw new RequestError('request body too large');
92
+ chunks.push(bytes);
93
+ }
94
+ try {
95
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
96
+ }
97
+ catch {
98
+ throw new RequestError('invalid JSON');
99
+ }
100
+ }
101
+ function reply(res, code, body) {
102
+ res.statusCode = code;
103
+ res.end(JSON.stringify(body));
104
+ }
105
+ //# sourceMappingURL=human-inbox-transport.js.map
@@ -0,0 +1,83 @@
1
+ import { type HumanAction, type InboxSenderAddress, type InboxMessage, type UserAddress } from './domain.js';
2
+ import type { LearningLedger } from './ledger.js';
3
+ export interface HumanNotificationRequest {
4
+ notificationId: string;
5
+ messageId: string;
6
+ recipient: UserAddress;
7
+ role: 'to' | 'cc';
8
+ fromAddress: InboxSenderAddress;
9
+ subject: string;
10
+ body: string;
11
+ priority: 'normal' | 'urgent';
12
+ due?: string;
13
+ humanAction?: HumanAction;
14
+ }
15
+ export type HumanNotificationOutcome = {
16
+ status: 'sent';
17
+ providerReceipt: string;
18
+ } | {
19
+ status: 'rejected';
20
+ reason: string;
21
+ } | {
22
+ status: 'unknown';
23
+ };
24
+ export interface HumanNotificationProvider {
25
+ /** Return sent only after verifying an actual channel success receipt. */
26
+ notify(request: HumanNotificationRequest): Promise<HumanNotificationOutcome>;
27
+ }
28
+ export type HumanNotificationPolicyDecision = {
29
+ action: 'send';
30
+ } | {
31
+ action: 'defer';
32
+ } | {
33
+ action: 'suppress';
34
+ reason: string;
35
+ };
36
+ export interface HumanNotificationRunnerOptions {
37
+ /** Host attention policy runs before reservation; defer leaves the Inbox item pending. */
38
+ decide?(message: InboxMessage, recipient: UserAddress): HumanNotificationPolicyDecision | Promise<HumanNotificationPolicyDecision>;
39
+ }
40
+ export interface HumanNotificationPass {
41
+ sent: Array<{
42
+ messageId: string;
43
+ recipient: UserAddress;
44
+ }>;
45
+ rejected: Array<{
46
+ messageId: string;
47
+ recipient: UserAddress;
48
+ }>;
49
+ unknown: Array<{
50
+ messageId: string;
51
+ recipient: UserAddress;
52
+ }>;
53
+ deferred: Array<{
54
+ messageId: string;
55
+ recipient: UserAddress;
56
+ }>;
57
+ suppressed: Array<{
58
+ messageId: string;
59
+ recipient: UserAddress;
60
+ }>;
61
+ failed: Array<{
62
+ messageId: string;
63
+ recipient: UserAddress;
64
+ error: unknown;
65
+ }>;
66
+ }
67
+ /** At most one external attempt per message and user. A reserved but unresolved attempt remains unknown. */
68
+ export declare class HumanNotificationRunner {
69
+ private readonly ledger;
70
+ private readonly provider;
71
+ private readonly options;
72
+ private pass;
73
+ private closing;
74
+ constructor(ledger: LearningLedger, provider: HumanNotificationProvider, options?: HumanNotificationRunnerOptions);
75
+ recoverBatch(limit?: number): Promise<HumanNotificationPass>;
76
+ /** Only a definite provider rejection can be retried, by an explicit host call. */
77
+ retryRejected(messageId: string, recipient: UserAddress): Promise<'sent' | 'rejected' | 'unknown'>;
78
+ private runPass;
79
+ private decision;
80
+ private notify;
81
+ close(): Promise<void>;
82
+ }
83
+ //# sourceMappingURL=human-notification-runner.d.ts.map
@@ -0,0 +1,138 @@
1
+ import { isUserAddress } from './domain.js';
2
+ /** At most one external attempt per message and user. A reserved but unresolved attempt remains unknown. */
3
+ export class HumanNotificationRunner {
4
+ ledger;
5
+ provider;
6
+ options;
7
+ pass;
8
+ closing = false;
9
+ constructor(ledger, provider, options = {}) {
10
+ this.ledger = ledger;
11
+ this.provider = provider;
12
+ this.options = options;
13
+ }
14
+ recoverBatch(limit = 4) {
15
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
16
+ throw new Error('human notification pass limit must be between 1 and 100');
17
+ }
18
+ if (this.closing)
19
+ return Promise.resolve({ sent: [], rejected: [], unknown: [],
20
+ deferred: [], suppressed: [], failed: [] });
21
+ if (this.pass)
22
+ return this.pass;
23
+ const task = this.runPass(limit);
24
+ this.pass = task;
25
+ void task.finally(() => { if (this.pass === task)
26
+ this.pass = undefined; }).catch(() => undefined);
27
+ return task;
28
+ }
29
+ /** Only a definite provider rejection can be retried, by an explicit host call. */
30
+ async retryRejected(messageId, recipient) {
31
+ if (this.closing)
32
+ throw new Error('human notification runner is closed');
33
+ const message = this.ledger.getInboxMessage(messageId);
34
+ if (message === undefined || ![...message.to, ...message.cc].includes(recipient)) {
35
+ throw new Error('human notification message is unavailable to this recipient');
36
+ }
37
+ const decision = await this.decision(message, recipient);
38
+ if (decision.action !== 'send')
39
+ throw new Error('human notification policy does not permit retry');
40
+ const notification = await this.ledger.retryRejectedInboxHumanNotification(messageId, recipient);
41
+ return this.notify(message, recipient, notification);
42
+ }
43
+ async runPass(limit) {
44
+ const result = { sent: [], rejected: [], unknown: [],
45
+ deferred: [], suppressed: [], failed: [] };
46
+ let attempts = 0;
47
+ for (const message of this.ledger.listInboxMessages().reverse()) {
48
+ if (this.closing || attempts >= limit)
49
+ break;
50
+ const recipients = [...new Set([...message.to, ...message.cc])];
51
+ for (const recipient of recipients) {
52
+ if (this.closing || attempts >= limit)
53
+ break;
54
+ if (!isUserAddress(recipient))
55
+ continue;
56
+ const delivery = this.ledger.getInboxDelivery(message.id, recipient);
57
+ if (delivery?.status !== 'delivered' || delivery.handledAt !== undefined
58
+ || this.ledger.getInboxHumanNotification(message.id, recipient) !== undefined)
59
+ continue;
60
+ try {
61
+ const optOut = await this.ledger.suppressInboxHumanNotificationIfOptedOut(message.id, recipient);
62
+ if (optOut) {
63
+ result.suppressed.push({ messageId: message.id, recipient });
64
+ continue;
65
+ }
66
+ const decision = await this.decision(message, recipient);
67
+ if (decision.action === 'defer') {
68
+ result.deferred.push({ messageId: message.id, recipient });
69
+ continue;
70
+ }
71
+ if (decision.action === 'suppress') {
72
+ await this.ledger.suppressInboxHumanNotification(message.id, recipient, decision.reason);
73
+ result.suppressed.push({ messageId: message.id, recipient });
74
+ continue;
75
+ }
76
+ }
77
+ catch (error) {
78
+ result.failed.push({ messageId: message.id, recipient, error });
79
+ continue;
80
+ }
81
+ let reserved;
82
+ try {
83
+ reserved = await this.ledger.reserveInboxHumanNotification(message.id, recipient);
84
+ }
85
+ catch (error) {
86
+ result.failed.push({ messageId: message.id, recipient, error });
87
+ continue;
88
+ }
89
+ if (!reserved.reserved) {
90
+ if (reserved.notification.status === 'suppressed') {
91
+ result.suppressed.push({ messageId: message.id, recipient });
92
+ }
93
+ continue;
94
+ }
95
+ attempts++;
96
+ try {
97
+ const status = await this.notify(message, recipient, reserved.notification);
98
+ result[status].push({ messageId: message.id, recipient });
99
+ }
100
+ catch (error) {
101
+ // The channel may have accepted the request. Leave the durable reservation unknown.
102
+ result.failed.push({ messageId: message.id, recipient, error });
103
+ }
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+ async decision(message, recipient) {
109
+ const result = await this.options.decide?.(message, recipient) ?? { action: 'send' };
110
+ if (result.action !== 'send' && result.action !== 'defer'
111
+ && (result.action !== 'suppress' || !result.reason?.trim())) {
112
+ throw new Error('human notification policy returned an invalid decision');
113
+ }
114
+ return result;
115
+ }
116
+ async notify(message, recipient, notification) {
117
+ const outcome = await this.provider.notify({
118
+ notificationId: notification.id, messageId: message.id,
119
+ recipient, role: message.to.includes(recipient) ? 'to' : 'cc',
120
+ fromAddress: message.fromAddress, subject: message.subject, body: message.body,
121
+ priority: message.priority,
122
+ ...(message.due !== undefined ? { due: message.due } : {}),
123
+ ...(message.humanAction !== undefined ? { humanAction: message.humanAction } : {}),
124
+ });
125
+ if (outcome.status === 'unknown')
126
+ return 'unknown';
127
+ const settled = await this.ledger.settleInboxHumanNotification(message.id, recipient, outcome);
128
+ if (settled.status === 'suppressed')
129
+ throw new Error('suppressed human notification cannot settle');
130
+ return settled.status;
131
+ }
132
+ async close() {
133
+ this.closing = true;
134
+ if (this.pass)
135
+ await this.pass;
136
+ }
137
+ }
138
+ //# sourceMappingURL=human-notification-runner.js.map
@@ -0,0 +1,18 @@
1
+ import type { HumanNotificationOutcome, HumanNotificationProvider, HumanNotificationRequest } from './human-notification-runner.js';
2
+ export interface HumanNotificationWebhookOptions {
3
+ endpoint: string;
4
+ /** The host obtains a current credential from its secret store. */
5
+ authorization: () => string | Promise<string>;
6
+ timeoutMs?: number;
7
+ fetch?: typeof fetch;
8
+ }
9
+ /** Generic channel adapter: only an explicit provider receipt can confirm a push. */
10
+ export declare class HumanNotificationWebhook implements HumanNotificationProvider {
11
+ private readonly options;
12
+ private readonly endpoint;
13
+ private readonly request;
14
+ private readonly timeoutMs;
15
+ constructor(options: HumanNotificationWebhookOptions);
16
+ notify(request: HumanNotificationRequest): Promise<HumanNotificationOutcome>;
17
+ }
18
+ //# sourceMappingURL=human-notification-webhook.d.ts.map
@@ -0,0 +1,102 @@
1
+ const MAX_RESPONSE_BYTES = 64 * 1024;
2
+ /** Generic channel adapter: only an explicit provider receipt can confirm a push. */
3
+ export class HumanNotificationWebhook {
4
+ options;
5
+ endpoint;
6
+ request;
7
+ timeoutMs;
8
+ constructor(options) {
9
+ this.options = options;
10
+ const endpoint = new URL(options.endpoint);
11
+ if (endpoint.protocol !== 'https:' && !(endpoint.protocol === 'http:'
12
+ && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) {
13
+ throw new Error('human notification webhook requires HTTPS or loopback HTTP');
14
+ }
15
+ if (endpoint.username || endpoint.password || endpoint.hash) {
16
+ throw new Error('human notification webhook endpoint cannot contain credentials or a fragment');
17
+ }
18
+ const timeoutMs = options.timeoutMs ?? 10_000;
19
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 250 || timeoutMs > 30_000) {
20
+ throw new Error('human notification webhook timeout must be 250 to 30000 ms');
21
+ }
22
+ this.endpoint = endpoint;
23
+ this.request = options.fetch ?? fetch;
24
+ this.timeoutMs = timeoutMs;
25
+ }
26
+ async notify(request) {
27
+ const authorization = await this.options.authorization();
28
+ if (!authorization.trim() || /[\r\n]/.test(authorization)) {
29
+ throw new Error('human notification webhook credential is unavailable');
30
+ }
31
+ let response;
32
+ try {
33
+ response = await this.request(this.endpoint, {
34
+ method: 'POST',
35
+ headers: { authorization, 'content-type': 'application/json' },
36
+ body: JSON.stringify({ schemaVersion: 1, ...request }),
37
+ // Never forward the Authorization header to a redirect target.
38
+ redirect: 'error',
39
+ signal: AbortSignal.timeout(this.timeoutMs),
40
+ });
41
+ }
42
+ catch {
43
+ return { status: 'unknown' };
44
+ }
45
+ if (!response.ok) {
46
+ await response.body?.cancel().catch(() => undefined);
47
+ return { status: 'unknown' };
48
+ }
49
+ const body = await boundedJson(response);
50
+ if (!body || typeof body !== 'object' || Array.isArray(body))
51
+ return { status: 'unknown' };
52
+ const record = body;
53
+ if (record['status'] === 'sent' && typeof record['receiptId'] === 'string'
54
+ && record['receiptId'].trim()) {
55
+ return { status: 'sent', providerReceipt: record['receiptId'].trim() };
56
+ }
57
+ if (record['status'] === 'rejected' && typeof record['reason'] === 'string'
58
+ && record['reason'].trim()) {
59
+ return { status: 'rejected', reason: record['reason'].trim() };
60
+ }
61
+ return { status: 'unknown' };
62
+ }
63
+ }
64
+ async function boundedJson(response) {
65
+ if (!response.body)
66
+ return undefined;
67
+ const reader = response.body.getReader();
68
+ const chunks = [];
69
+ let total = 0;
70
+ try {
71
+ while (true) {
72
+ const item = await reader.read();
73
+ if (item.done)
74
+ break;
75
+ total += item.value.byteLength;
76
+ if (total > MAX_RESPONSE_BYTES) {
77
+ await reader.cancel().catch(() => undefined);
78
+ return undefined;
79
+ }
80
+ chunks.push(item.value);
81
+ }
82
+ const bytes = new Uint8Array(total);
83
+ let offset = 0;
84
+ for (const chunk of chunks) {
85
+ bytes.set(chunk, offset);
86
+ offset += chunk.byteLength;
87
+ }
88
+ try {
89
+ return JSON.parse(new TextDecoder().decode(bytes));
90
+ }
91
+ catch {
92
+ return undefined;
93
+ }
94
+ }
95
+ catch {
96
+ return undefined;
97
+ }
98
+ finally {
99
+ reader.releaseLock();
100
+ }
101
+ }
102
+ //# sourceMappingURL=human-notification-webhook.js.map
@@ -0,0 +1,61 @@
1
+ import { type SessionEvent, type SessionHeader, type SessionId } from '@deepseek-ai/dsh-session';
2
+ import { z } from 'zod';
3
+ import { type SessionBinding, type UserAddress } from './domain.js';
4
+ import type { LearningLedger } from './ledger.js';
5
+ declare const CandidateSchema: z.ZodObject<{
6
+ sessionId: z.ZodString;
7
+ turn: z.ZodNumber;
8
+ }, z.core.$strict>;
9
+ export type HumanTurnCandidate = z.infer<typeof CandidateSchema>;
10
+ declare const DestinationSchema: z.ZodObject<{
11
+ recipient: z.ZodString;
12
+ authorityRef: z.ZodString;
13
+ pagePath: z.ZodString;
14
+ summary: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strict>;
16
+ export type HumanTurnDestination = Omit<z.infer<typeof DestinationSchema>, 'recipient'> & {
17
+ recipient: UserAddress;
18
+ };
19
+ export interface HumanTurnCompletionSource {
20
+ /** A durable, paginated list of turns with a host-verified person as asker. */
21
+ listCandidates(after: string | undefined, limit: number): Promise<{
22
+ candidates: HumanTurnCandidate[];
23
+ nextCursor?: string;
24
+ }>;
25
+ /** Recheck the exact turn's asker and access to its conversation on every attempt. */
26
+ authorize(candidate: HumanTurnCandidate, binding: SessionBinding): HumanTurnDestination | undefined | Promise<HumanTurnDestination | undefined>;
27
+ }
28
+ export interface HumanTurnPersistence {
29
+ /** Read flushed Session events, including the completed turn. */
30
+ inspect(id: SessionId): Promise<{
31
+ meta: SessionHeader;
32
+ events: readonly SessionEvent[];
33
+ }>;
34
+ }
35
+ export interface HumanTurnCompletionPass {
36
+ delivered: string[];
37
+ existing: string[];
38
+ held: string[];
39
+ skipped: string[];
40
+ failed: Array<{
41
+ sessionId: string;
42
+ turn: number;
43
+ error: unknown;
44
+ }>;
45
+ nextCursor?: string;
46
+ }
47
+ /** Bridges one verified human turn into Inbox. External delivery remains a separate, opt-out-aware step. */
48
+ export declare class HumanTurnCompletionNotifier {
49
+ private readonly inbox;
50
+ private readonly sessions;
51
+ private readonly source;
52
+ constructor(inbox: LearningLedger, sessions: HumanTurnPersistence, source: HumanTurnCompletionSource);
53
+ recoverBatch(after?: string, limit?: number): Promise<HumanTurnCompletionPass>;
54
+ notify(candidateInput: HumanTurnCandidate): Promise<'delivered' | 'existing' | 'held' | 'skipped'>;
55
+ private repairDelivery;
56
+ }
57
+ export declare function humanTurnInboxMessageId(sessionId: string, turn: number): string;
58
+ export declare function humanTurnSubject(summary?: string): string;
59
+ export declare function humanTurnBody(summary?: string): string;
60
+ export {};
61
+ //# sourceMappingURL=human-turn-completion.d.ts.map