@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
@@ -0,0 +1,11 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export declare const name = "bizagent-local-human-host";
3
+ export declare const inject: string[];
4
+ /**
5
+ * Local single-user adapter: one person reviews Projects and proposals and answers their Inbox.
6
+ * In DSH Web that person is whoever is signed in to DSH in the browser (DSH's own login), and
7
+ * they act as the BizAgent owner (or $BIZAGENT_LOCAL_HUMAN_ID). Scripts can instead present a
8
+ * Bearer token from $BIZAGENT_LOCAL_HUMAN_TOKEN_FILE. BIZAGENT_LOCAL_HUMAN=off turns this off.
9
+ */
10
+ export declare function apply(ctx: Context): Promise<void>;
11
+ //# sourceMappingURL=local-human-host.d.ts.map
@@ -0,0 +1,191 @@
1
+ import { registerHumanInboxRoute } from './human-inbox-transport.js';
2
+ import { isLocalUserId } from './local-owner.js';
3
+ import { LocalHumanCredential, readLocalHumanToken } from './local-human-token.js';
4
+ import { workspaceRegistry } from './organization-workspace.js';
5
+ import { registerProjectReviewRoute } from './project-review-transport.js';
6
+ import { registerProposalReviewRoute } from './proposal-review-transport.js';
7
+ import { dshOperatorAuthentication, guardLocalRoute } from './route-guard.js';
8
+ import { registerSharedKnowledgeReviewRoute } from './shared-knowledge-review-transport.js';
9
+ import { BIZAGENT_AGENT_SESSION_API, BIZAGENT_LOCAL_HUMAN_STATUS_API } from './ui-contract.js';
10
+ export const name = 'bizagent-local-human-host';
11
+ export const inject = ['bizagent', 'bizagentAgentSessions', 'webServer'];
12
+ /**
13
+ * Local single-user adapter: one person reviews Projects and proposals and answers their Inbox.
14
+ * In DSH Web that person is whoever is signed in to DSH in the browser (DSH's own login), and
15
+ * they act as the BizAgent owner (or $BIZAGENT_LOCAL_HUMAN_ID). Scripts can instead present a
16
+ * Bearer token from $BIZAGENT_LOCAL_HUMAN_TOKEN_FILE. BIZAGENT_LOCAL_HUMAN=off turns this off.
17
+ */
18
+ export async function apply(ctx) {
19
+ if (process.env.BIZAGENT_LOCAL_HUMAN === 'off')
20
+ return;
21
+ const tokenFile = process.env.BIZAGENT_LOCAL_HUMAN_TOKEN_FILE?.trim();
22
+ const userId = process.env.BIZAGENT_LOCAL_HUMAN_ID?.trim() || ctx.bizagent.config.owner;
23
+ if (!isLocalUserId(userId)) {
24
+ throw new Error('BIZAGENT_LOCAL_HUMAN_ID must use 1-100 letters, numbers, dots, underscores, or hyphens');
25
+ }
26
+ if (ctx.webServer.host !== '127.0.0.1') {
27
+ // A configured token is a promise to protect review routes, so a public bind is an error.
28
+ if (tokenFile)
29
+ throw new Error('Local human review requires DSH Web to bind 127.0.0.1');
30
+ return;
31
+ }
32
+ const credential = tokenFile ? new LocalHumanCredential(await readLocalHumanToken(tokenFile)) : undefined;
33
+ const authenticate = (request) => {
34
+ const header = request.headers.authorization;
35
+ if (typeof header === 'string' && header.startsWith('Bearer ')) {
36
+ return credential?.matchesToken(header.slice('Bearer '.length)) ? { userId } : null;
37
+ }
38
+ const operator = dshOperatorAuthentication(ctx);
39
+ return operator !== undefined && operator.requestRejection(request) === undefined ? { userId } : null;
40
+ };
41
+ // DSH Web lists only Sessions attached to a Workspace. Sessions this plugin opens are created on
42
+ // the host, so attach them to a Workspace named after their Home, as DSH does for its own.
43
+ const showInSidebar = async (homeAddress, sessionId) => {
44
+ const path = ctx.bizagent.store.homeWorkspacePath(homeAddress);
45
+ const registry = workspaceRegistry(ctx);
46
+ if (registry !== undefined) {
47
+ const workspace = await registry.create(path, ctx.bizagent.store.getHome(homeAddress).displayName);
48
+ await workspace.attachSession(sessionId);
49
+ }
50
+ return path;
51
+ };
52
+ const ownsHome = (address) => {
53
+ try {
54
+ return ctx.bizagent.store.getHome(address).owner === userId;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ };
60
+ const authorizeMutation = (_request, principal) => principal.userId === userId;
61
+ const disposers = [
62
+ ctx.webServer.register({ kind: 'exact', path: BIZAGENT_LOCAL_HUMAN_STATUS_API,
63
+ handler: guardLocalRoute(async (request, response) => {
64
+ response.setHeader('Cache-Control', 'no-store');
65
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
66
+ response.setHeader('X-Content-Type-Options', 'nosniff');
67
+ if (request.method !== 'GET') {
68
+ response.writeHead(405, { Allow: 'GET' });
69
+ response.end(JSON.stringify({ error: 'method-not-allowed' }));
70
+ return;
71
+ }
72
+ const principal = authenticate(request);
73
+ response.statusCode = principal ? 200 : 401;
74
+ response.end(JSON.stringify(principal ? { userId: principal.userId } : { error: 'authentication-required' }));
75
+ }) }),
76
+ ctx.webServer.register({ kind: 'exact', path: BIZAGENT_AGENT_SESSION_API,
77
+ handler: guardLocalRoute(async (request, response) => {
78
+ response.setHeader('Cache-Control', 'no-store');
79
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
80
+ response.setHeader('X-Content-Type-Options', 'nosniff');
81
+ const reply = (status, body) => {
82
+ response.statusCode = status;
83
+ response.end(JSON.stringify(body));
84
+ };
85
+ if (request.method !== 'POST') {
86
+ reply(405, { error: 'method-not-allowed' });
87
+ return;
88
+ }
89
+ if (!authenticate(request)) {
90
+ reply(401, { error: 'authentication-required' });
91
+ return;
92
+ }
93
+ if (!String(request.headers['content-type'] ?? '').toLowerCase().startsWith('application/json')) {
94
+ reply(415, { error: 'content-type' });
95
+ return;
96
+ }
97
+ try {
98
+ const parts = [];
99
+ let total = 0;
100
+ for await (const chunk of request) {
101
+ const part = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
102
+ total += part.byteLength;
103
+ if (total > 4096) {
104
+ reply(413, { error: 'body-too-large' });
105
+ return;
106
+ }
107
+ parts.push(part);
108
+ }
109
+ const body = JSON.parse(Buffer.concat(parts).toString('utf8'));
110
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
111
+ reply(400, { error: 'invalid-request' });
112
+ return;
113
+ }
114
+ const input = body;
115
+ const principal = typeof input.homeAddress === 'string' && input.projectId === undefined
116
+ && Object.keys(input).length === 1;
117
+ const main = typeof input.projectId === 'string' && input.homeAddress === undefined
118
+ && Object.keys(input).length === 1;
119
+ const existing = typeof input.sessionId === 'string' && input.sessionId.length > 0
120
+ && Object.keys(input).length === 1;
121
+ if (!principal && !main && !existing) {
122
+ reply(400, { error: 'invalid-request' });
123
+ return;
124
+ }
125
+ if (existing) {
126
+ const binding = ctx.bizagent.ledger.getBinding(input.sessionId);
127
+ if (!binding || !ownsHome(binding.homeAddress)) {
128
+ reply(403, { error: 'session-forbidden' });
129
+ return;
130
+ }
131
+ // Reveal a host-created worker without resuming its Agent or changing an ordinary
132
+ // conversation's existing Workspace. Runtime ownership remains with its runner.
133
+ const homePath = ctx.bizagent.store.homeWorkspacePath(binding.homeAddress);
134
+ const workspacePath = binding.cwd === homePath
135
+ ? await showInSidebar(binding.homeAddress, input.sessionId) : undefined;
136
+ reply(200, { sessionId: input.sessionId, ...(workspacePath ? { workspacePath } : {}) });
137
+ }
138
+ else if (principal) {
139
+ if (!ownsHome(input.homeAddress)) {
140
+ reply(403, { error: 'home-forbidden' });
141
+ return;
142
+ }
143
+ const result = await ctx.bizagentAgentSessions.openPrincipal(input.homeAddress);
144
+ reply(200, { ...result, workspacePath: await showInSidebar(result.homeAddress, result.sessionId) });
145
+ }
146
+ else {
147
+ const project = ctx.bizagent.projectLedger?.get(input.projectId);
148
+ if (!project || !ownsHome(project.lead)) {
149
+ reply(403, { error: 'project-forbidden' });
150
+ return;
151
+ }
152
+ const result = await ctx.bizagentAgentSessions.openProjectMain(input.projectId);
153
+ reply(200, { ...result, workspacePath: await showInSidebar(result.homeAddress, result.sessionId) });
154
+ }
155
+ }
156
+ catch {
157
+ reply(409, { error: 'agent-session-unavailable' });
158
+ }
159
+ }) }),
160
+ registerProjectReviewRoute(ctx.webServer, ctx.bizagent, {
161
+ authenticateUser: authenticate,
162
+ authorizeMutation,
163
+ authorizeReview: (principal, project) => principal.userId === userId && ownsHome(project.lead),
164
+ }),
165
+ registerProposalReviewRoute(ctx.webServer, ctx.bizagent, {
166
+ authenticateHuman: authenticate,
167
+ authorizeMutation,
168
+ authorizeProposal: (_request, principal, proposal, action) => principal.userId === userId && ownsHome(proposal.toAddress)
169
+ ? `local-human:${userId}:${proposal.id}:${action}` : null,
170
+ }),
171
+ registerSharedKnowledgeReviewRoute(ctx.webServer, ctx.bizagent, {
172
+ authenticateHuman: authenticate,
173
+ authorizeMutation,
174
+ authorizeProposal: (_request, principal, proposal, action) => {
175
+ if (principal.userId !== userId)
176
+ return null;
177
+ const authorized = proposal.target.scope === 'line'
178
+ ? ownsHome(`line:${proposal.target.line}`)
179
+ : process.env.BIZAGENT_LOCAL_PLATFORM_ADMIN === '1';
180
+ return authorized ? `local-human:${userId}:${proposal.target.scope}:${proposal.id}:${action}` : null;
181
+ },
182
+ }),
183
+ registerHumanInboxRoute(ctx.webServer, ctx.bizagent, {
184
+ authenticateUser: authenticate,
185
+ authorizeMutation,
186
+ }),
187
+ ];
188
+ ctx.effect(() => () => { for (const dispose of disposers.reverse())
189
+ dispose(); });
190
+ }
191
+ //# sourceMappingURL=local-human-host.js.map
@@ -0,0 +1,9 @@
1
+ /** Read a token file that must be a private regular file owned by the current user. */
2
+ export declare function readLocalHumanToken(file: string): Promise<string>;
3
+ /** Verifies a presented Bearer token against one local token without leaking timing. */
4
+ export declare class LocalHumanCredential {
5
+ private readonly tokenDigest;
6
+ constructor(token: string);
7
+ matchesToken(candidate: string): boolean;
8
+ }
9
+ //# sourceMappingURL=local-human-token.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ /** Read a token file that must be a private regular file owned by the current user. */
4
+ export async function readLocalHumanToken(file) {
5
+ const info = await stat(file);
6
+ if (!info.isFile() || (process.platform !== 'win32' && (info.mode & 0o077) !== 0)) {
7
+ throw new Error('Local human token file must be a private regular file (mode 0600)');
8
+ }
9
+ if (typeof process.getuid === 'function' && info.uid !== process.getuid()) {
10
+ throw new Error('Local human token file must belong to the current user');
11
+ }
12
+ const token = (await readFile(file, 'utf8')).trim();
13
+ if (!isTokenShape(token))
14
+ throw new Error('Local human token must contain 32-512 non-whitespace characters');
15
+ return token;
16
+ }
17
+ function isTokenShape(value) {
18
+ return value.length >= 32 && value.length <= 512 && !/\s/u.test(value);
19
+ }
20
+ /** Verifies a presented Bearer token against one local token without leaking timing. */
21
+ export class LocalHumanCredential {
22
+ tokenDigest;
23
+ constructor(token) {
24
+ this.tokenDigest = createHash('sha256').update(token).digest();
25
+ }
26
+ matchesToken(candidate) {
27
+ if (!isTokenShape(candidate))
28
+ return false;
29
+ return timingSafeEqual(createHash('sha256').update(candidate).digest(), this.tokenDigest);
30
+ }
31
+ }
32
+ //# sourceMappingURL=local-human-token.js.map
@@ -0,0 +1,8 @@
1
+ /** Whether a string can name a local human (Home owner or reviewer). */
2
+ export declare function isLocalUserId(value: string): boolean;
3
+ /**
4
+ * The person who owns Homes created by a local host: $BIZAGENT_LOCAL_HUMAN_ID when set,
5
+ * otherwise the operating system user name reduced to the allowed characters.
6
+ */
7
+ export declare function localOwnerId(env?: NodeJS.ProcessEnv): string;
8
+ //# sourceMappingURL=local-owner.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { userInfo } from 'node:os';
2
+ const USER_ID = /^[a-zA-Z0-9._-]{1,100}$/;
3
+ /** Whether a string can name a local human (Home owner or reviewer). */
4
+ export function isLocalUserId(value) {
5
+ return USER_ID.test(value);
6
+ }
7
+ /**
8
+ * The person who owns Homes created by a local host: $BIZAGENT_LOCAL_HUMAN_ID when set,
9
+ * otherwise the operating system user name reduced to the allowed characters.
10
+ */
11
+ export function localOwnerId(env = process.env) {
12
+ const configured = env['BIZAGENT_LOCAL_HUMAN_ID']?.trim();
13
+ if (configured) {
14
+ if (!isLocalUserId(configured))
15
+ throw new TypeError('BIZAGENT_LOCAL_HUMAN_ID must use 1-100 letters, numbers, dots, underscores, or hyphens');
16
+ return configured;
17
+ }
18
+ let name = '';
19
+ try {
20
+ name = userInfo().username;
21
+ }
22
+ catch { /* Some containers have no passwd entry. */ }
23
+ const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, '').slice(0, 100);
24
+ return cleaned || 'local';
25
+ }
26
+ //# sourceMappingURL=local-owner.js.map
@@ -0,0 +1,10 @@
1
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm';
2
+ declare module '@deepseek-ai/dsh-llm' {
3
+ interface MessageSourceMap {
4
+ bizagent: {
5
+ kind: 'bizagent';
6
+ } & ContextFormed;
7
+ }
8
+ }
9
+ export declare const BIZAGENT_SOURCE_KIND = "bizagent";
10
+ //# sourceMappingURL=message-source.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const BIZAGENT_SOURCE_KIND = 'bizagent';
2
+ //# sourceMappingURL=message-source.js.map
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const ActiveOrganizationLocationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ schemaVersion: z.ZodLiteral<1>;
4
+ workspacePath: z.ZodString;
5
+ organizationId: z.ZodString;
6
+ organizationRoot: z.ZodString;
7
+ updatedAt: z.ZodString;
8
+ }, z.core.$strip>, z.ZodObject<{
9
+ schemaVersion: z.ZodLiteral<2>;
10
+ organizationId: z.ZodString;
11
+ organizationRoot: z.ZodString;
12
+ updatedAt: z.ZodString;
13
+ }, z.core.$strip>], "schemaVersion">;
14
+ export type ActiveOrganizationLocation = z.infer<typeof ActiveOrganizationLocationSchema>;
15
+ export declare function activeOrganizationLocationPath(registryRoot: string): string;
16
+ /** Where a new organization lives: `<data root>/orgs/<id>`. */
17
+ export declare function organizationLocation(registryRoot: string, organizationId: string): ActiveOrganizationLocation;
18
+ export declare function loadActiveOrganizationLocation(registryRoot: string): Promise<ActiveOrganizationLocation | undefined>;
19
+ export declare function saveActiveOrganizationLocation(registryRoot: string, location: ActiveOrganizationLocation): Promise<void>;
20
+ //# sourceMappingURL=organization-location.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isAbsolute, join, relative, resolve } from 'node:path';
3
+ import YAML from 'yaml';
4
+ import { z } from 'zod';
5
+ import { nowIso } from './domain.js';
6
+ import { assertKnownFormat } from './known-format.js';
7
+ import { writeFileDurably } from './safe-file.js';
8
+ const ORGANIZATION_ID = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/);
9
+ /** Version 1 placed an organization at `<workspacePath>/bizagent/<id>` inside a DSH Workspace. */
10
+ const LocationV1Schema = z.object({
11
+ schemaVersion: z.literal(1),
12
+ workspacePath: z.string().min(1),
13
+ organizationId: ORGANIZATION_ID,
14
+ organizationRoot: z.string().min(1),
15
+ updatedAt: z.string().min(1),
16
+ });
17
+ /** Version 2 places every organization at `<data root>/orgs/<id>`. */
18
+ const LocationV2Schema = z.object({
19
+ schemaVersion: z.literal(2),
20
+ organizationId: ORGANIZATION_ID,
21
+ organizationRoot: z.string().min(1),
22
+ updatedAt: z.string().min(1),
23
+ });
24
+ export const ActiveOrganizationLocationSchema = z.discriminatedUnion('schemaVersion', [LocationV1Schema, LocationV2Schema]);
25
+ export function activeOrganizationLocationPath(registryRoot) {
26
+ return join(resolve(registryRoot), 'active-organization.yaml');
27
+ }
28
+ /** Where a new organization lives: `<data root>/orgs/<id>`. */
29
+ export function organizationLocation(registryRoot, organizationId) {
30
+ const root = resolve(registryRoot);
31
+ if (!isAbsolute(root))
32
+ throw new Error('BizAgent data root must be absolute');
33
+ const id = ORGANIZATION_ID.parse(organizationId.trim().toLocaleLowerCase());
34
+ return LocationV2Schema.parse({
35
+ schemaVersion: 2,
36
+ organizationId: id,
37
+ organizationRoot: join(root, 'orgs', id),
38
+ updatedAt: nowIso(),
39
+ });
40
+ }
41
+ function legacyOrganizationRoot(workspacePath, organizationId) {
42
+ const trimmedWorkspace = workspacePath.trim();
43
+ if (!isAbsolute(trimmedWorkspace))
44
+ throw new Error('DSH Workspace path must be absolute');
45
+ const workspace = resolve(trimmedWorkspace);
46
+ const organizationRoot = resolve(workspace, 'bizagent', organizationId);
47
+ const rel = relative(workspace, organizationRoot);
48
+ if (rel.startsWith('..') || isAbsolute(rel))
49
+ throw new Error('organization directory escapes the DSH Workspace');
50
+ return organizationRoot;
51
+ }
52
+ export async function loadActiveOrganizationLocation(registryRoot) {
53
+ let raw;
54
+ try {
55
+ raw = YAML.parse(await readFile(activeOrganizationLocationPath(registryRoot), 'utf8'));
56
+ }
57
+ catch (error) {
58
+ if (isNotFound(error))
59
+ return undefined;
60
+ throw error;
61
+ }
62
+ assertKnownFormat(raw, 2, 'active-organization.yaml');
63
+ const location = ActiveOrganizationLocationSchema.parse(raw);
64
+ const expected = location.schemaVersion === 1
65
+ ? legacyOrganizationRoot(location.workspacePath, location.organizationId)
66
+ : organizationLocation(registryRoot, location.organizationId).organizationRoot;
67
+ if (expected !== resolve(location.organizationRoot)) {
68
+ throw new Error(`active organization root does not match its expected location: ${location.organizationRoot}`);
69
+ }
70
+ return location.schemaVersion === 1
71
+ ? { ...location, organizationRoot: expected, workspacePath: resolve(location.workspacePath) }
72
+ : { ...location, organizationRoot: expected };
73
+ }
74
+ export async function saveActiveOrganizationLocation(registryRoot, location) {
75
+ await writeFileDurably(activeOrganizationLocationPath(registryRoot), YAML.stringify(location, { lineWidth: 0 }));
76
+ }
77
+ function isNotFound(error) {
78
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
79
+ }
80
+ //# sourceMappingURL=organization-location.js.map
@@ -0,0 +1,22 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { BizAgentService } from './service.js';
3
+ /** The slice of DSH's host `workspaceRegistry` BizAgent uses to show its folders in the sidebar. */
4
+ export interface WorkspaceRegistryLike {
5
+ list(): ReadonlyArray<{
6
+ readonly id: string;
7
+ readonly path: string;
8
+ }>;
9
+ create(path: string, title?: string): Promise<{
10
+ readonly id: string;
11
+ attachSession(sessionId: string): Promise<void>;
12
+ }>;
13
+ insertBefore(id: string, beforeId?: string): Promise<readonly string[]>;
14
+ }
15
+ /** DSH's Workspace registry when this host composes one. */
16
+ export declare function workspaceRegistry(ctx: Context): WorkspaceRegistryLike | undefined;
17
+ /**
18
+ * Show the active organization's folder as a DSH Workspace named after the organization. The first
19
+ * time it is added it goes to the top of the sidebar; after that its place is the person's choice.
20
+ */
21
+ export declare function showOrganizationWorkspace(ctx: Context, service: BizAgentService): Promise<void>;
22
+ //# sourceMappingURL=organization-workspace.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { realpathSync } from 'node:fs';
2
+ /** DSH's Workspace registry when this host composes one. */
3
+ export function workspaceRegistry(ctx) {
4
+ return ctx.get('workspaceRegistry');
5
+ }
6
+ /**
7
+ * Show the active organization's folder as a DSH Workspace named after the organization. The first
8
+ * time it is added it goes to the top of the sidebar; after that its place is the person's choice.
9
+ */
10
+ export async function showOrganizationWorkspace(ctx, service) {
11
+ const root = service.store.organizationRoot;
12
+ const organization = service.store.getOrganization();
13
+ const registry = workspaceRegistry(ctx);
14
+ if (root === undefined || organization === undefined || registry === undefined)
15
+ return;
16
+ // The registry keeps canonical paths; a temporary folder may sit behind a symlink.
17
+ let canonical = root;
18
+ try {
19
+ canonical = realpathSync.native(root);
20
+ }
21
+ catch { /* created on registration */ }
22
+ if (registry.list().some(workspace => workspace.path === canonical))
23
+ return;
24
+ const known = new Set(registry.list().map(workspace => workspace.id));
25
+ const workspace = await registry.create(root, organization.name);
26
+ if (known.has(workspace.id))
27
+ return;
28
+ const first = registry.list()[0];
29
+ if (first !== undefined && first.id !== workspace.id)
30
+ await registry.insertBefore(workspace.id, first.id);
31
+ }
32
+ //# sourceMappingURL=organization-workspace.js.map
@@ -0,0 +1,183 @@
1
+ import { type DomainFacility } from '@deepseek-ai/dsh-storage-domain';
2
+ import { z } from 'zod';
3
+ import { type HomeAddress } from './domain.js';
4
+ export declare const ProjectSeatSchema: z.ZodObject<{
5
+ id: z.ZodString;
6
+ address: z.ZodString;
7
+ homeAddress: z.ZodString;
8
+ responsibility: z.ZodString;
9
+ status: z.ZodEnum<{
10
+ active: "active";
11
+ released: "released";
12
+ }>;
13
+ joinedAt: z.ZodString;
14
+ updatedAt: z.ZodString;
15
+ releasedAt: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ export type ProjectSeat = Omit<z.infer<typeof ProjectSeatSchema>, 'homeAddress'> & {
18
+ homeAddress: HomeAddress;
19
+ };
20
+ export declare const FormalProjectSchema: z.ZodObject<{
21
+ schemaVersion: z.ZodLiteral<1>;
22
+ id: z.ZodString;
23
+ idempotencyKey: z.ZodString;
24
+ lead: z.ZodString;
25
+ driver: z.ZodOptional<z.ZodString>;
26
+ createdBySessionId: z.ZodString;
27
+ title: z.ZodString;
28
+ goal: z.ZodString;
29
+ doneWhen: z.ZodString;
30
+ status: z.ZodEnum<{
31
+ open: "open";
32
+ accepted: "accepted";
33
+ awaiting_acceptance: "awaiting_acceptance";
34
+ }>;
35
+ revision: z.ZodNumber;
36
+ claims: z.ZodArray<z.ZodObject<{
37
+ id: z.ZodString;
38
+ summary: z.ZodString;
39
+ body: z.ZodString;
40
+ doneWhenAssessment: z.ZodString;
41
+ work: z.ZodArray<z.ZodObject<{
42
+ id: z.ZodString;
43
+ status: z.ZodEnum<{
44
+ completed: "completed";
45
+ cancelled: "cancelled";
46
+ }>;
47
+ resultMessageId: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strip>>;
49
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
50
+ path: z.ZodString;
51
+ sha256: z.ZodString;
52
+ bytes: z.ZodNumber;
53
+ snapshot: z.ZodLiteral<true>;
54
+ }, z.core.$strip>>>;
55
+ submittedBySessionId: z.ZodString;
56
+ submittedAt: z.ZodString;
57
+ }, z.core.$strip>>;
58
+ reviews: z.ZodArray<z.ZodObject<{
59
+ claimId: z.ZodString;
60
+ decision: z.ZodEnum<{
61
+ accept: "accept";
62
+ rework: "rework";
63
+ }>;
64
+ reason: z.ZodString;
65
+ reviewedBySessionId: z.ZodOptional<z.ZodString>;
66
+ reviewedByPrincipal: z.ZodOptional<z.ZodString>;
67
+ reviewedAt: z.ZodString;
68
+ }, z.core.$strip>>;
69
+ seats: z.ZodOptional<z.ZodArray<z.ZodObject<{
70
+ id: z.ZodString;
71
+ address: z.ZodString;
72
+ homeAddress: z.ZodString;
73
+ responsibility: z.ZodString;
74
+ status: z.ZodEnum<{
75
+ active: "active";
76
+ released: "released";
77
+ }>;
78
+ joinedAt: z.ZodString;
79
+ updatedAt: z.ZodString;
80
+ releasedAt: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strip>>>;
82
+ seatEvents: z.ZodOptional<z.ZodArray<z.ZodObject<{
83
+ seatAddress: z.ZodString;
84
+ homeAddress: z.ZodString;
85
+ action: z.ZodEnum<{
86
+ released: "released";
87
+ joined: "joined";
88
+ "responsibility-updated": "responsibility-updated";
89
+ rejoined: "rejoined";
90
+ }>;
91
+ responsibility: z.ZodString;
92
+ bySessionId: z.ZodString;
93
+ at: z.ZodString;
94
+ }, z.core.$strip>>>;
95
+ currentClaimId: z.ZodOptional<z.ZodString>;
96
+ acceptedClaimId: z.ZodOptional<z.ZodString>;
97
+ createdAt: z.ZodString;
98
+ updatedAt: z.ZodString;
99
+ }, z.core.$strip>;
100
+ export type FormalProject = Omit<z.infer<typeof FormalProjectSchema>, 'lead' | 'seats'> & {
101
+ lead: HomeAddress;
102
+ seats?: ProjectSeat[];
103
+ };
104
+ export declare const BIZAGENT_PROJECT_DOMAIN: {
105
+ name: string;
106
+ version: number;
107
+ tables: {
108
+ projects: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
109
+ schemaVersion: 1;
110
+ id: string;
111
+ idempotencyKey: string;
112
+ lead: string;
113
+ createdBySessionId: string;
114
+ title: string;
115
+ goal: string;
116
+ doneWhen: string;
117
+ status: "open" | "accepted" | "awaiting_acceptance";
118
+ revision: number;
119
+ claims: {
120
+ id: string;
121
+ summary: string;
122
+ body: string;
123
+ doneWhenAssessment: string;
124
+ work: {
125
+ id: string;
126
+ status: "completed" | "cancelled";
127
+ resultMessageId?: string | undefined;
128
+ }[];
129
+ submittedBySessionId: string;
130
+ submittedAt: string;
131
+ artifacts?: {
132
+ path: string;
133
+ sha256: string;
134
+ bytes: number;
135
+ snapshot: true;
136
+ }[] | undefined;
137
+ }[];
138
+ reviews: {
139
+ claimId: string;
140
+ decision: "accept" | "rework";
141
+ reason: string;
142
+ reviewedAt: string;
143
+ reviewedBySessionId?: string | undefined;
144
+ reviewedByPrincipal?: string | undefined;
145
+ }[];
146
+ createdAt: string;
147
+ updatedAt: string;
148
+ driver?: string | undefined;
149
+ seats?: {
150
+ id: string;
151
+ address: string;
152
+ homeAddress: string;
153
+ responsibility: string;
154
+ status: "active" | "released";
155
+ joinedAt: string;
156
+ updatedAt: string;
157
+ releasedAt?: string | undefined;
158
+ }[] | undefined;
159
+ seatEvents?: {
160
+ seatAddress: string;
161
+ homeAddress: string;
162
+ action: "released" | "joined" | "responsibility-updated" | "rejoined";
163
+ responsibility: string;
164
+ bySessionId: string;
165
+ at: string;
166
+ }[] | undefined;
167
+ currentClaimId?: string | undefined;
168
+ acceptedClaimId?: string | undefined;
169
+ }>;
170
+ };
171
+ };
172
+ export declare class ProjectLedger {
173
+ private readonly domain;
174
+ private mutationTail;
175
+ private constructor();
176
+ static open(facility: DomainFacility): Promise<ProjectLedger>;
177
+ withWrite<T>(update: () => Promise<T>): Promise<T>;
178
+ get(id: string): FormalProject | undefined;
179
+ list(): FormalProject[];
180
+ put(project: FormalProject): Promise<void>;
181
+ close(): Promise<void>;
182
+ }
183
+ //# sourceMappingURL=project-ledger.d.ts.map