@superpanel-sdk/runtime 0.2.3 → 0.5.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.
- package/README.md +28 -8
- package/dist/builder/codegen/emit.d.ts +29 -11
- package/dist/builder/codegen/emit.js +328 -61
- package/dist/builder/codegen/generate.d.ts +21 -0
- package/dist/builder/codegen/generate.js +152 -20
- package/dist/builder/company-import-graph.d.ts +2 -0
- package/dist/builder/company-import-graph.js +40 -8
- package/dist/builder/company-imports.d.ts +24 -0
- package/dist/builder/company-imports.js +114 -0
- package/dist/builder/compile/compile-sequence.d.ts +59 -8
- package/dist/builder/compile/compile-sequence.js +279 -77
- package/dist/builder/compile/function-code.d.ts +12 -2
- package/dist/builder/compile/function-code.js +87 -39
- package/dist/builder/compile/registry.d.ts +7 -3
- package/dist/builder/compile/registry.js +24 -23
- package/dist/builder/load.d.ts +70 -10
- package/dist/builder/load.js +250 -61
- package/dist/builder/paths.d.ts +3 -0
- package/dist/builder/paths.js +16 -0
- package/dist/builder/schedules.d.ts +23 -0
- package/dist/builder/schedules.js +102 -0
- package/dist/builder/slot-schema-validation.d.ts +4 -0
- package/dist/builder/slot-schema-validation.js +10 -0
- package/dist/builder/typecheck.d.ts +8 -0
- package/dist/builder/typecheck.js +105 -0
- package/dist/builder/types.d.ts +91 -19
- package/dist/builder/types.js +2 -1
- package/dist/cli/auth/auth.d.ts +37 -0
- package/dist/cli/auth/auth.js +211 -0
- package/dist/cli/auth/backend-url.d.ts +5 -0
- package/dist/cli/auth/backend-url.js +25 -0
- package/dist/cli/auth/clipboard.d.ts +9 -0
- package/dist/cli/auth/clipboard.js +38 -0
- package/dist/cli/auth/http.d.ts +20 -0
- package/dist/cli/auth/http.js +27 -0
- package/dist/cli/auth/index.d.ts +10 -0
- package/dist/cli/auth/index.js +27 -0
- package/dist/cli/auth/progress.d.ts +12 -0
- package/dist/cli/auth/progress.js +15 -0
- package/dist/cli/auth/serve-login.d.ts +8 -0
- package/dist/cli/auth/serve-login.js +24 -0
- package/dist/cli/compile/index.js +3 -0
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.js +52 -0
- package/dist/cli/index.js +28 -8
- package/dist/cli/serve/index.d.ts +73 -3
- package/dist/cli/serve/index.js +365 -116
- package/dist/cli/type-gen/index.js +9 -8
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +7 -0
- package/dist/compute-host/compute-host.d.ts +15 -23
- package/dist/compute-host/compute-host.js +25 -28
- package/dist/compute-host/index.d.ts +2 -4
- package/dist/compute-host/index.js +1 -2
- package/dist/compute-host/source-compute-host.d.ts +11 -9
- package/dist/compute-host/source-compute-host.js +46 -32
- package/dist/core/case-orchestrator/contact-hours.d.ts +50 -0
- package/dist/core/case-orchestrator/contact-hours.js +108 -0
- package/dist/core/case-orchestrator/follow-ups.d.ts +31 -0
- package/dist/core/case-orchestrator/follow-ups.js +103 -0
- package/dist/core/case-orchestrator/index.d.ts +6 -0
- package/dist/core/case-orchestrator/index.js +6 -0
- package/dist/core/case-orchestrator/render.d.ts +75 -0
- package/dist/core/case-orchestrator/render.js +239 -0
- package/dist/core/case-orchestrator/time.d.ts +11 -0
- package/dist/core/case-orchestrator/time.js +29 -0
- package/dist/core/case-orchestrator/tools.d.ts +29 -0
- package/dist/core/case-orchestrator/tools.js +31 -0
- package/dist/core/case-orchestrator/types.d.ts +753 -0
- package/dist/core/case-orchestrator/types.js +46 -0
- package/dist/core/case-orchestrator/validate.d.ts +11 -0
- package/dist/core/case-orchestrator/validate.js +63 -0
- package/dist/core/context/call.d.ts +291 -0
- package/dist/core/context/call.js +94 -0
- package/dist/core/context/channels.d.ts +60 -0
- package/dist/core/context/channels.js +35 -0
- package/dist/core/context/contact.d.ts +96 -6
- package/dist/core/context/contact.js +48 -2
- package/dist/core/context/email-message.d.ts +159 -0
- package/dist/core/context/email-message.js +59 -0
- package/dist/core/context/email.d.ts +97 -0
- package/dist/core/context/email.js +91 -0
- package/dist/core/context/exchanges.d.ts +142 -0
- package/dist/core/context/exchanges.js +63 -0
- package/dist/core/context/instances.d.ts +278 -0
- package/dist/core/context/instances.js +123 -0
- package/dist/core/context/kv.d.ts +113 -0
- package/dist/core/context/kv.js +60 -0
- package/dist/core/context/platform-registry.d.ts +509 -4
- package/dist/core/context/platform-registry.js +14 -2
- package/dist/core/context/platform.d.ts +2 -1
- package/dist/core/context/sms.d.ts +147 -0
- package/dist/core/context/sms.js +57 -0
- package/dist/core/context/types.d.ts +51 -48
- package/dist/core/context/types.js +1 -7
- package/dist/core/conversation/index.d.ts +31 -0
- package/dist/core/conversation/index.js +14 -0
- package/dist/core/functions/define/bind.d.ts +25 -0
- package/dist/core/functions/define/bind.js +14 -0
- package/dist/core/functions/define/case.d.ts +9 -0
- package/dist/core/functions/define/case.js +13 -0
- package/dist/core/functions/define/events.d.ts +20 -0
- package/dist/core/functions/define/events.js +21 -0
- package/dist/core/functions/define/no-case.d.ts +4 -0
- package/dist/core/functions/define/no-case.js +9 -0
- package/dist/core/functions/events.d.ts +247 -0
- package/dist/core/functions/events.js +32 -0
- package/dist/core/functions/func-ref.d.ts +11 -2
- package/dist/core/functions/schedules.d.ts +51 -0
- package/dist/core/functions/schedules.js +20 -0
- package/dist/core/functions/types.d.ts +62 -51
- package/dist/core/functions/types.js +6 -1
- package/dist/core/office-hours/index.d.ts +39 -0
- package/dist/core/office-hours/index.js +187 -0
- package/dist/core/page/define.d.ts +834 -0
- package/dist/core/page/define.js +477 -0
- package/dist/core/page/ops.d.ts +58 -0
- package/dist/core/page/ops.js +220 -0
- package/dist/core/page/render.d.ts +253 -0
- package/dist/core/page/render.js +840 -0
- package/dist/core/page/time-format.d.ts +19 -0
- package/dist/core/page/time-format.js +41 -0
- package/dist/core/page/timeline.d.ts +40 -0
- package/dist/core/page/timeline.js +2 -0
- package/dist/core/page/typed.d.ts +642 -0
- package/dist/core/page/typed.js +2 -0
- package/dist/core/page/workspace.d.ts +33 -0
- package/dist/core/page/workspace.js +231 -0
- package/dist/core/party/index.d.ts +3 -0
- package/dist/core/party/index.js +3 -0
- package/dist/core/party/schema.d.ts +137 -0
- package/dist/core/party/schema.js +80 -0
- package/dist/core/party/types.d.ts +40 -0
- package/dist/core/party/types.js +15 -0
- package/dist/core/sequence/define.d.ts +57 -0
- package/dist/core/sequence/define.js +10 -0
- package/dist/core/sequence/index.d.ts +63 -0
- package/dist/core/sequence/index.js +231 -0
- package/dist/core/slot/binding.d.ts +40 -0
- package/dist/core/slot/binding.js +2 -0
- package/dist/core/slot/company-metadata.d.ts +113 -0
- package/dist/core/slot/company-metadata.js +563 -0
- package/dist/core/slot/compile-schema.d.ts +19 -13
- package/dist/core/slot/compile-schema.js +51 -248
- package/dist/core/slot/contact-timezone.d.ts +110 -0
- package/dist/core/slot/contact-timezone.js +186 -0
- package/dist/core/slot/field.d.ts +2 -24
- package/dist/core/slot/field.js +0 -69
- package/dist/core/slot/id-ref.d.ts +17 -2
- package/dist/core/slot/id-ref.js +16 -8
- package/dist/core/slot/index.d.ts +12 -13
- package/dist/core/slot/index.js +11 -12
- package/dist/core/slot/naming.d.ts +15 -0
- package/dist/core/slot/naming.js +96 -0
- package/dist/core/slot/platform-fields.d.ts +119 -0
- package/dist/core/slot/platform-fields.js +121 -0
- package/dist/core/slot/ref.d.ts +16 -0
- package/dist/core/slot/ref.js +14 -0
- package/dist/core/slot/row.d.ts +2 -9
- package/dist/core/slot/row.js +13 -23
- package/dist/core/slot/schema-compat.d.ts +16 -0
- package/dist/core/slot/schema-compat.js +112 -0
- package/dist/core/slot/schema.d.ts +409 -76
- package/dist/core/slot/schema.js +178 -10
- package/dist/core/slot/types.d.ts +27 -94
- package/dist/core/slot/value.d.ts +26 -3
- package/dist/core/slot/value.js +89 -57
- package/dist/dev-overlay/protocol.d.ts +41 -8
- package/dist/fn-context/build-context.d.ts +3 -1
- package/dist/fn-context/build-context.js +20 -10
- package/dist/fn-context/fake/empty-api.js +9 -28
- package/dist/fn-context/fake/index.d.ts +11 -4
- package/dist/fn-context/fake/index.js +27 -2
- package/dist/fn-context/fn-params.d.ts +6 -0
- package/dist/fn-context/fn-params.js +13 -0
- package/dist/fn-context/index.d.ts +5 -5
- package/dist/fn-context/index.js +4 -4
- package/dist/fn-context/log.js +2 -2
- package/dist/fn-context/make-ctx.d.ts +11 -0
- package/dist/fn-context/make-ctx.js +18 -8
- package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +24 -0
- package/dist/fn-context/rpc/case-orchestrator-tools.js +13 -0
- package/dist/fn-context/rpc/index.d.ts +3 -1
- package/dist/fn-context/rpc/index.js +3 -1
- package/dist/fn-context/rpc/integration/index.d.ts +4 -4
- package/dist/fn-context/rpc/integration/index.js +1 -1
- package/dist/fn-context/rpc/integration/list-surfaces.d.ts +21 -0
- package/dist/fn-context/rpc/integration/{list-bindings.js → list-surfaces.js} +4 -4
- package/dist/fn-context/rpc/platform.d.ts +2 -2
- package/dist/fn-context/rpc/platform.js +1 -1
- package/dist/fn-context/rpc/records.d.ts +78 -0
- package/dist/fn-context/rpc/records.js +26 -0
- package/dist/fn-context/rpc/trpc-client.d.ts +2 -2
- package/dist/fn-context/rpc/trpc-client.js +88 -7
- package/dist/fn-context/rpc/trpc-init.d.ts +2 -2
- package/dist/fn-context/rpc/trpc-router-factory.d.ts +49 -3
- package/dist/fn-context/rpc/trpc-router-factory.js +2 -0
- package/dist/fn-context/run-scope.d.ts +4 -0
- package/dist/fn-context/run-scope.js +9 -0
- package/dist/fn-context/types.d.ts +36 -3
- package/dist/index.d.ts +37 -46
- package/dist/index.js +33 -33
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +41 -0
- package/dist/platform.d.ts +76 -0
- package/dist/platform.js +56 -0
- package/dist/voice-service/actions.d.ts +71 -4
- package/dist/voice-service/types.d.ts +42 -5
- package/dist/worker.d.ts +10 -0
- package/dist/worker.js +10 -0
- package/package.json +38 -19
- package/dist/builder/compile/compile-agenda.d.ts +0 -15
- package/dist/builder/compile/compile-agenda.js +0 -64
- package/dist/builder/merge.d.ts +0 -4
- package/dist/builder/merge.js +0 -19
- package/dist/compute-host/read-only-cache.d.ts +0 -20
- package/dist/compute-host/read-only-cache.js +0 -61
- package/dist/core/context/db.d.ts +0 -69
- package/dist/core/context/db.js +0 -44
- package/dist/core/context/docusign.d.ts +0 -134
- package/dist/core/context/docusign.js +0 -108
- package/dist/core/context/filevine.d.ts +0 -323
- package/dist/core/context/filevine.js +0 -254
- package/dist/core/context/http.d.ts +0 -9
- package/dist/core/context/http.js +0 -4
- package/dist/core/context/hubspot.d.ts +0 -250
- package/dist/core/context/hubspot.js +0 -191
- package/dist/core/context/twilio.d.ts +0 -50
- package/dist/core/context/twilio.js +0 -42
- package/dist/core/functions/define/case-context.d.ts +0 -14
- package/dist/core/functions/define/case-context.js +0 -15
- package/dist/core/functions/define/compute.d.ts +0 -7
- package/dist/core/functions/define/compute.js +0 -17
- package/dist/core/functions/define/custom.d.ts +0 -4
- package/dist/core/functions/define/custom.js +0 -8
- package/dist/core/functions/define/system.d.ts +0 -7
- package/dist/core/functions/define/system.js +0 -12
- package/dist/core/slot/deps.d.ts +0 -15
- package/dist/core/slot/deps.js +0 -88
- package/dist/core/slot/derived.d.ts +0 -32
- package/dist/core/slot/derived.js +0 -115
- package/dist/core/slot/platform-session-fields.d.ts +0 -38
- package/dist/core/slot/platform-session-fields.js +0 -48
- package/dist/core/slot/sf-schema.d.ts +0 -23
- package/dist/core/slot/sf-schema.js +0 -125
- package/dist/core/slot/sf-validate.d.ts +0 -14
- package/dist/core/slot/sf-validate.js +0 -132
- package/dist/core/slot/snapshot.d.ts +0 -18
- package/dist/core/slot/snapshot.js +0 -138
- package/dist/core/slot/write.d.ts +0 -48
- package/dist/core/slot/write.js +0 -426
- package/dist/core/stage/builders.d.ts +0 -54
- package/dist/core/stage/builders.js +0 -31
- package/dist/core/stage/condition.d.ts +0 -17
- package/dist/core/stage/condition.js +0 -198
- package/dist/core/stage/drive.d.ts +0 -43
- package/dist/core/stage/drive.js +0 -78
- package/dist/core/stage/finish-when.d.ts +0 -20
- package/dist/core/stage/finish-when.js +0 -82
- package/dist/core/stage/flatten.d.ts +0 -27
- package/dist/core/stage/flatten.js +0 -466
- package/dist/core/stage/index.d.ts +0 -15
- package/dist/core/stage/index.js +0 -13
- package/dist/core/stage/interrupts.d.ts +0 -28
- package/dist/core/stage/interrupts.js +0 -120
- package/dist/core/stage/ivr-fields.d.ts +0 -9
- package/dist/core/stage/ivr-fields.js +0 -75
- package/dist/core/stage/simulate.d.ts +0 -24
- package/dist/core/stage/simulate.js +0 -33
- package/dist/core/stage/transfer-fields.d.ts +0 -19
- package/dist/core/stage/transfer-fields.js +0 -129
- package/dist/core/stage/transfer-spec.d.ts +0 -48
- package/dist/core/stage/transfer-spec.js +0 -113
- package/dist/core/stage/types.d.ts +0 -208
- package/dist/core/stage/types.js +0 -2
- package/dist/core/stage/validate.d.ts +0 -11
- package/dist/core/stage/validate.js +0 -330
- package/dist/core/stage/walk.d.ts +0 -39
- package/dist/core/stage/walk.js +0 -119
- package/dist/fn-context/rpc/integration/list-bindings.d.ts +0 -21
- package/dist/fn-context/system-args.d.ts +0 -6
- package/dist/fn-context/system-args.js +0 -14
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
|
-
import { checkAll,
|
|
4
|
-
async function runOnce(repoRoot) {
|
|
5
|
-
const results = await generateAll(repoRoot);
|
|
3
|
+
import { checkAll, companyGenPaths, generateAll, listSequenceIds } from '../../builder/codegen/generate.js';
|
|
4
|
+
async function runOnce(repoRoot, sequenceIds) {
|
|
5
|
+
const results = await generateAll(repoRoot, { sequenceIds });
|
|
6
|
+
if (!sequenceIds) {
|
|
7
|
+
for (const outPath of Object.values(companyGenPaths(repoRoot)))
|
|
8
|
+
console.log(`wrote ${path.relative(repoRoot, outPath)}`);
|
|
9
|
+
}
|
|
6
10
|
for (const result of results) {
|
|
7
11
|
console.log(`wrote ${path.relative(repoRoot, result.outPath)}`);
|
|
8
12
|
console.log(`wrote ${path.relative(repoRoot, result.caseContextOutPath)}`);
|
|
13
|
+
console.log(`wrote ${path.relative(repoRoot, result.indexOutPath)}`);
|
|
9
14
|
}
|
|
10
15
|
}
|
|
11
16
|
/** `sequences/<id>/…` → sequence id */
|
|
@@ -36,11 +41,7 @@ async function runWatch(repoRoot) {
|
|
|
36
41
|
await runOnce(repoRoot);
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
const result = await generateSequence(repoRoot, sequenceId);
|
|
41
|
-
console.log(`wrote ${path.relative(repoRoot, result.outPath)}`);
|
|
42
|
-
console.log(`wrote ${path.relative(repoRoot, result.caseContextOutPath)}`);
|
|
43
|
-
}
|
|
44
|
+
await runOnce(repoRoot, [...job.sequences]);
|
|
44
45
|
}
|
|
45
46
|
catch (error) {
|
|
46
47
|
console.error('[type:gen] regenerate failed:', error instanceof Error ? error.message : error);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the platform's compile child loads from the runtime the company repo installed
|
|
3
|
+
* (`packages/services/src/release/compile-child.ts`). Platform-only: the package resolves
|
|
4
|
+
* `./compiler` under the `superpanel-platform` condition, and the child loads it by file path.
|
|
5
|
+
*/
|
|
6
|
+
export { compileRepo, type CompileRepoEvent, type CompileRepoOptions, type CompileRepoSequenceResult, } from './builder/compile/compile-sequence.js';
|
|
7
|
+
//# sourceMappingURL=compiler.d.ts.map
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the platform's compile child loads from the runtime the company repo installed
|
|
3
|
+
* (`packages/services/src/release/compile-child.ts`). Platform-only: the package resolves
|
|
4
|
+
* `./compiler` under the `superpanel-platform` condition, and the child loads it by file path.
|
|
5
|
+
*/
|
|
6
|
+
export { compileRepo, } from './builder/compile/compile-sequence.js';
|
|
7
|
+
//# sourceMappingURL=compiler.js.map
|
|
@@ -1,38 +1,30 @@
|
|
|
1
|
-
import type { CaseContextViewFn } from '../core/functions/define/case-context.js';
|
|
2
|
-
import type { RuntimeSlotsSnapshot } from '../core/slot/types.js';
|
|
3
1
|
/**
|
|
4
|
-
* Pluggable host for sequence functions (
|
|
2
|
+
* Pluggable host for sequence functions (case / noCase / orchestrator functions).
|
|
5
3
|
*
|
|
6
4
|
* - Local unit tests / company CI: {@link createLocalComputeHost} (direct JS call)
|
|
7
5
|
* - Author FuncMap: `createSourceComputeHost`
|
|
8
6
|
* - Platform backend: QuickJS / Cloudflare Worker hosts with the same `execute` surface
|
|
9
7
|
*/
|
|
10
8
|
export interface ComputeHost {
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
* Compute/boolCompute/custom: `args[0]` is the raw snapshot (projected internally).
|
|
14
|
-
* `caseContextView`: `args[0]` is the raw snapshot (identity when the sequence has no view).
|
|
15
|
-
*/
|
|
16
|
-
execute(name: string, args?: unknown[]): Promise<unknown>;
|
|
9
|
+
/** Invoke a registered function with its type-specific invocation. */
|
|
10
|
+
execute(name: string, invocation?: ComputeInvocation): Promise<unknown>;
|
|
17
11
|
/** Release host-owned resources. No-op when the host has nothing to free. */
|
|
18
12
|
dispose(): void;
|
|
19
13
|
}
|
|
14
|
+
/** Input for case, noCase and orchestrator functions. */
|
|
15
|
+
export type FunctionInvocation = {
|
|
16
|
+
caseContext?: unknown;
|
|
17
|
+
params?: object;
|
|
18
|
+
};
|
|
19
|
+
export type ComputeInvocation = FunctionInvocation;
|
|
20
|
+
export declare function parseComputeInvocation(value: unknown): ComputeInvocation | null;
|
|
20
21
|
/**
|
|
21
|
-
* Name →
|
|
22
|
-
* `
|
|
22
|
+
* Name → plain function of `caseContext` (local tests stand in sequence functions with these).
|
|
23
|
+
* Parameter typed as `never` so company-narrowed maps remain assignable (TS contravariance).
|
|
23
24
|
*/
|
|
24
|
-
export type ComputeFnMap = Record<string, (
|
|
25
|
+
export type ComputeFnMap = Record<string, (caseContext: never) => unknown | Promise<unknown>>;
|
|
25
26
|
export type ComputeHostOrFns = ComputeHost | ComputeFnMap;
|
|
26
|
-
export
|
|
27
|
-
/**
|
|
28
|
-
* Optional `(slots) => CaseContext` projector. Registered under the view.ts
|
|
29
|
-
* export name `caseContextView` and invoked before each compute fn
|
|
30
|
-
* (no cross-call cache — snapshot may change mid-recompute).
|
|
31
|
-
*/
|
|
32
|
-
caseContextView?: CaseContextViewFn<RuntimeSlotsSnapshot, unknown>;
|
|
33
|
-
};
|
|
34
|
-
/** In-process host: call plain JS computeFns. No VM, no I/O fence. */
|
|
35
|
-
export declare function createLocalComputeHost(fns: ComputeFnMap, options?: LocalComputeHostOptions): ComputeHost;
|
|
27
|
+
export declare function createLocalComputeHost(fns: ComputeFnMap): ComputeHost;
|
|
36
28
|
/** Accept either a host or a name→fn map (map is wrapped as local host). */
|
|
37
|
-
export declare function resolveComputeHost(hostOrFns: ComputeHostOrFns
|
|
29
|
+
export declare function resolveComputeHost(hostOrFns: ComputeHostOrFns): ComputeHost;
|
|
38
30
|
//# sourceMappingURL=compute-host.d.ts.map
|
|
@@ -1,45 +1,42 @@
|
|
|
1
|
+
export function parseComputeInvocation(value) {
|
|
2
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
3
|
+
return null;
|
|
4
|
+
const record = value;
|
|
5
|
+
if (record.params !== undefined &&
|
|
6
|
+
(typeof record.params !== 'object' || record.params === null || Array.isArray(record.params))) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
...(Object.hasOwn(record, 'caseContext') ? { caseContext: record.caseContext } : {}),
|
|
11
|
+
...(record.params !== undefined ? { params: record.params } : {}),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
1
14
|
function isComputeHost(value) {
|
|
2
15
|
return typeof value.execute === 'function';
|
|
3
16
|
}
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
throw new Error(`Function ${name}: args[0] (slots) required`);
|
|
17
|
+
function requireCaseContext(name, invocation) {
|
|
18
|
+
if (!Object.hasOwn(invocation, 'caseContext')) {
|
|
19
|
+
throw new Error(`Function ${name}: caseContext required`);
|
|
8
20
|
}
|
|
9
|
-
return
|
|
21
|
+
return invocation.caseContext;
|
|
10
22
|
}
|
|
11
|
-
|
|
12
|
-
export function createLocalComputeHost(fns, options = {}) {
|
|
13
|
-
if (Object.hasOwn(fns, 'caseContextView')) {
|
|
14
|
-
throw new Error(`computeFn name 'caseContextView' collides with the sequence view`);
|
|
15
|
-
}
|
|
16
|
-
const viewFn = options.caseContextView ?? ((slots) => slots);
|
|
17
|
-
const registry = {
|
|
18
|
-
caseContextView: { kind: 'caseContextView', main: viewFn },
|
|
19
|
-
};
|
|
20
|
-
for (const [name, fn] of Object.entries(fns)) {
|
|
21
|
-
registry[name] = { kind: 'compute', main: fn };
|
|
22
|
-
}
|
|
23
|
+
export function createLocalComputeHost(fns) {
|
|
23
24
|
return {
|
|
24
|
-
async execute(name,
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
throw new Error(`Missing
|
|
28
|
-
}
|
|
29
|
-
const snapshot = requireSlots(name, args);
|
|
30
|
-
if (def.kind === 'caseContextView') {
|
|
31
|
-
return def.main(snapshot);
|
|
25
|
+
async execute(name, invocation = {}) {
|
|
26
|
+
const fn = fns[name];
|
|
27
|
+
if (!fn) {
|
|
28
|
+
throw new Error(`Missing function implementation: ${name}`);
|
|
32
29
|
}
|
|
33
|
-
return
|
|
30
|
+
return fn(requireCaseContext(name, invocation));
|
|
34
31
|
},
|
|
35
32
|
dispose() { },
|
|
36
33
|
};
|
|
37
34
|
}
|
|
38
35
|
/** Accept either a host or a name→fn map (map is wrapped as local host). */
|
|
39
|
-
export function resolveComputeHost(hostOrFns
|
|
36
|
+
export function resolveComputeHost(hostOrFns) {
|
|
40
37
|
if (isComputeHost(hostOrFns)) {
|
|
41
38
|
return hostOrFns;
|
|
42
39
|
}
|
|
43
|
-
return createLocalComputeHost(hostOrFns
|
|
40
|
+
return createLocalComputeHost(hostOrFns);
|
|
44
41
|
}
|
|
45
42
|
//# sourceMappingURL=compute-host.js.map
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export { createLocalComputeHost, resolveComputeHost } from './compute-host.js';
|
|
2
|
-
export type { ComputeFnMap, ComputeHost, ComputeHostOrFns,
|
|
1
|
+
export { createLocalComputeHost, parseComputeInvocation, resolveComputeHost } from './compute-host.js';
|
|
2
|
+
export type { ComputeFnMap, ComputeHost, ComputeHostOrFns, ComputeInvocation, FunctionInvocation, } from './compute-host.js';
|
|
3
3
|
export { createSourceComputeHost } from './source-compute-host.js';
|
|
4
4
|
export type { SourceComputeHost, SourceComputeHostOptions } from './source-compute-host.js';
|
|
5
|
-
export { readOnlyComputeNames, withReadOnlyComputeCache } from './read-only-cache.js';
|
|
6
|
-
export type { ReadOnlyComputeCacheOptions } from './read-only-cache.js';
|
|
7
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { createLocalComputeHost, resolveComputeHost } from './compute-host.js';
|
|
1
|
+
export { createLocalComputeHost, parseComputeInvocation, resolveComputeHost } from './compute-host.js';
|
|
2
2
|
export { createSourceComputeHost } from './source-compute-host.js';
|
|
3
|
-
export { readOnlyComputeNames, withReadOnlyComputeCache } from './read-only-cache.js';
|
|
4
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AnyEventHandlers } from '../core/functions/events.js';
|
|
2
2
|
import type { FuncMap } from '../core/functions/types.js';
|
|
3
3
|
import type { ComputeHost } from './compute-host.js';
|
|
4
|
-
import type {
|
|
5
|
-
import type { ActionDataSlice, ApiHost } from '../fn-context/types.js';
|
|
4
|
+
import type { ActionDataSlice, ApiHost, CaseOrchestratorApiHost } from '../fn-context/types.js';
|
|
6
5
|
export type SourceComputeHostOptions = {
|
|
7
6
|
functions: FuncMap;
|
|
8
|
-
|
|
7
|
+
/** Company `events` export; handlers are registered under the event name with the table-fixed kind. */
|
|
8
|
+
events?: AnyEventHandlers;
|
|
9
9
|
/** Override ApiHost; default Fake, or Rpc when runToken + data.config.backendUrl. */
|
|
10
|
-
apiHost?: ApiHost;
|
|
11
|
-
/** ActionDataSlice used to build ctx for
|
|
10
|
+
apiHost?: ApiHost & CaseOrchestratorApiHost;
|
|
11
|
+
/** ActionDataSlice used to build ctx for case / noCase / orchestrator. */
|
|
12
12
|
data?: ActionDataSlice;
|
|
13
13
|
/** When set with data.config.backendUrl, uses Http RpcApiHost. */
|
|
14
14
|
runToken?: string;
|
|
@@ -23,13 +23,15 @@ export type SourceComputeHostOptions = {
|
|
|
23
23
|
};
|
|
24
24
|
export interface SourceComputeHost extends ComputeHost {
|
|
25
25
|
}
|
|
26
|
+
/** Company `events` handlers as DefinedFunc entries (kind from the platform event table). */
|
|
27
|
+
export declare function eventsToFuncMap(events: AnyEventHandlers | undefined): FuncMap;
|
|
26
28
|
/**
|
|
27
29
|
* Local host: call author FuncMap implementations directly (no SequenceBuild.code).
|
|
28
30
|
*
|
|
29
31
|
* Lookup by name, then dispatch on `kind`:
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
32
|
-
* -
|
|
32
|
+
* - case: `fn(caseContext, { ...ctx, params })`; caseContext required.
|
|
33
|
+
* - orchestrator: same, with the Case Orchestrator ctx.
|
|
34
|
+
* - noCase: `fn({ ...ctx, params })`; caseContext must be absent.
|
|
33
35
|
*/
|
|
34
36
|
export declare function createSourceComputeHost(options: SourceComputeHostOptions): SourceComputeHost;
|
|
35
37
|
//# sourceMappingURL=source-compute-host.d.ts.map
|
|
@@ -1,63 +1,77 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildActionContext } from '../fn-context/build-context.js';
|
|
3
|
-
import {
|
|
1
|
+
import { isSystemEventName, systemEventKind } from '../core/functions/events.js';
|
|
2
|
+
import { buildActionContext, buildCaseOrchestratorContext } from '../fn-context/build-context.js';
|
|
3
|
+
import { resolveFnParams } from '../fn-context/fn-params.js';
|
|
4
4
|
import { createFakeApiHost } from '../fn-context/fake/index.js';
|
|
5
5
|
import { createRpcApiHost } from '../fn-context/rpc/index.js';
|
|
6
|
-
import { makeCtx } from '../fn-context/make-ctx.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { makeCaseOrchestratorCtx, makeCtx } from '../fn-context/make-ctx.js';
|
|
7
|
+
import { getLogger } from '../logger.js';
|
|
8
|
+
const logger = getLogger(['runtime', 'source']);
|
|
9
|
+
function requireCaseContext(name, invocation) {
|
|
10
|
+
if (!Object.hasOwn(invocation, 'caseContext'))
|
|
11
|
+
throw new Error(`Function ${name}: caseContext required`);
|
|
12
|
+
return invocation.caseContext;
|
|
13
|
+
}
|
|
14
|
+
/** Company `events` handlers as DefinedFunc entries (kind from the platform event table). */
|
|
15
|
+
export function eventsToFuncMap(events) {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const [name, handler] of Object.entries(events ?? {})) {
|
|
18
|
+
if (!isSystemEventName(name) || typeof handler !== 'function')
|
|
19
|
+
continue;
|
|
20
|
+
out[name] = { kind: systemEventKind(name), main: handler };
|
|
11
21
|
}
|
|
12
|
-
return
|
|
22
|
+
return out;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Local host: call author FuncMap implementations directly (no SequenceBuild.code).
|
|
16
26
|
*
|
|
17
27
|
* Lookup by name, then dispatch on `kind`:
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
28
|
+
* - case: `fn(caseContext, { ...ctx, params })`; caseContext required.
|
|
29
|
+
* - orchestrator: same, with the Case Orchestrator ctx.
|
|
30
|
+
* - noCase: `fn({ ...ctx, params })`; caseContext must be absent.
|
|
21
31
|
*/
|
|
22
32
|
export function createSourceComputeHost(options) {
|
|
23
33
|
const { functions, data } = options;
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
const eventFns = eventsToFuncMap(options.events);
|
|
35
|
+
for (const name of Object.keys(eventFns)) {
|
|
36
|
+
if (Object.hasOwn(functions, name)) {
|
|
37
|
+
throw new Error(`functions and events must not share names: ${name}`);
|
|
38
|
+
}
|
|
26
39
|
}
|
|
27
40
|
const apiHost = resolveApiHost(options);
|
|
28
|
-
const
|
|
29
|
-
const registry = {
|
|
30
|
-
...functions,
|
|
31
|
-
caseContextView: { kind: 'caseContextView', main: viewFn },
|
|
32
|
-
};
|
|
41
|
+
const registry = { ...functions, ...eventFns };
|
|
33
42
|
async function systemContext() {
|
|
34
43
|
return data ? buildActionContext(data, apiHost) : makeCtx();
|
|
35
44
|
}
|
|
45
|
+
async function caseOrchestratorContext() {
|
|
46
|
+
return data ? buildCaseOrchestratorContext(data, apiHost) : makeCaseOrchestratorCtx();
|
|
47
|
+
}
|
|
36
48
|
return {
|
|
37
|
-
async execute(name,
|
|
49
|
+
async execute(name, invocation = {}) {
|
|
38
50
|
const def = registry[name];
|
|
39
51
|
if (!def) {
|
|
40
52
|
throw new Error(`Missing function: ${name}`);
|
|
41
53
|
}
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
const functionInvocation = invocation;
|
|
55
|
+
const params = resolveFnParams(functionInvocation.params);
|
|
56
|
+
if (def.kind === 'case') {
|
|
57
|
+
const caseContext = requireCaseContext(name, functionInvocation);
|
|
58
|
+
const ctx = await systemContext();
|
|
59
|
+
return def.main(caseContext, { ...ctx, params });
|
|
44
60
|
}
|
|
45
|
-
if (def.kind === '
|
|
46
|
-
|
|
61
|
+
if (def.kind === 'orchestrator') {
|
|
62
|
+
const caseContext = requireCaseContext(name, functionInvocation);
|
|
63
|
+
const ctx = await caseOrchestratorContext();
|
|
64
|
+
return def.main(caseContext, { ...ctx, params: params });
|
|
47
65
|
}
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
const ctx = await systemContext();
|
|
51
|
-
const fn = def.main;
|
|
52
|
-
return fn(caseContext, { ...ctx, params: args[1] ?? {} });
|
|
66
|
+
if (Object.hasOwn(functionInvocation, 'caseContext')) {
|
|
67
|
+
throw new Error(`Function ${name}: noCase function must not receive caseContext`);
|
|
53
68
|
}
|
|
54
|
-
const params = resolveSystemFnParams(args);
|
|
55
69
|
const contextStarted = Date.now();
|
|
56
70
|
const ctx = await systemContext();
|
|
57
|
-
|
|
71
|
+
logger.info('context built', { name, durationMs: Date.now() - contextStarted });
|
|
58
72
|
const functionStarted = Date.now();
|
|
59
73
|
const result = await def.main({ ...ctx, params });
|
|
60
|
-
|
|
74
|
+
logger.info('function executed', { name, durationMs: Date.now() - functionStarted });
|
|
61
75
|
return result;
|
|
62
76
|
},
|
|
63
77
|
dispose() { },
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact hours in company code (25 §3): when a company may reach a contact on its own. The platform
|
|
3
|
+
* does not check them; the company writes its rules (by topic, state, channel) in the repository and
|
|
4
|
+
* books `at` with these helpers. Pure calendar math in the contact's local time, with no legal content
|
|
5
|
+
* (no built-in quiet hours). Part of the company API (`src/index.ts`), bundled into
|
|
6
|
+
* company code as is: no imports.
|
|
7
|
+
*/
|
|
8
|
+
export type ContactHoursWeekday = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
9
|
+
/** A local time range, `HH:MM` to `HH:MM` (`24:00` ends at midnight), start before end. */
|
|
10
|
+
export type ContactHoursRange = {
|
|
11
|
+
start: string;
|
|
12
|
+
end: string;
|
|
13
|
+
};
|
|
14
|
+
/** `OfficeHours` without `timezone`: always read in the contact's own zone. */
|
|
15
|
+
export type ContactHours = {
|
|
16
|
+
week: Record<ContactHoursWeekday, ContactHoursRange[]>;
|
|
17
|
+
/** Recurring dates (`MM-DD`) whose hours replace `week`; `[]` means no contact that day. */
|
|
18
|
+
holidays?: {
|
|
19
|
+
date: string;
|
|
20
|
+
hours: ContactHoursRange[];
|
|
21
|
+
}[];
|
|
22
|
+
/** One-off dates (`YYYY-MM-DD`) whose hours replace `holidays` and `week`. */
|
|
23
|
+
overrides?: {
|
|
24
|
+
date: string;
|
|
25
|
+
hours: ContactHoursRange[];
|
|
26
|
+
}[];
|
|
27
|
+
};
|
|
28
|
+
/** The contact's own `contact_schedule` value (when they said they may be reached). */
|
|
29
|
+
export type ContactScheduleValue = {
|
|
30
|
+
weekly: Record<ContactHoursWeekday, ContactHoursRange[]> | null;
|
|
31
|
+
dateOverrides: {
|
|
32
|
+
dates: string[];
|
|
33
|
+
windows: ContactHoursRange[];
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
/** Every day of the week with the same hours. */
|
|
37
|
+
export declare function everyDay(hours: ContactHoursRange[]): Record<ContactHoursWeekday, ContactHoursRange[]>;
|
|
38
|
+
/** A contact's `contact_schedule` as contact hours (no schedule: any time), to intersect with the company's. */
|
|
39
|
+
export declare function contactScheduleHours(schedule: ContactScheduleValue | null | undefined): ContactHours;
|
|
40
|
+
/** The hours on a local date (`YYYY-MM-DD`): an override, else a holiday, else the weekday. */
|
|
41
|
+
export declare function contactHoursOn(hours: ContactHours, day: string): ContactHoursRange[];
|
|
42
|
+
/** Whether `at` (a Date, ISO string or epoch ms) falls inside `hours` (one, or a list that must all allow it) in `timezone`. */
|
|
43
|
+
export declare function withinContactHours(hours: ContactHours | readonly ContactHours[], at: Date | string | number, timezone: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The first time at or after `from` (a Date, ISO string or epoch ms, e.g. `ctx.time.now`) inside `hours` (one, or a list that must all allow it: the
|
|
46
|
+
* company's rules and the contact's own schedule), in `timezone`, as an ISO string for `at`; null
|
|
47
|
+
* when there is none within `days` (default 14).
|
|
48
|
+
*/
|
|
49
|
+
export declare function nextContactTime(hours: ContactHours | readonly ContactHours[], from: Date | string | number, timezone: string, days?: number): string | null;
|
|
50
|
+
//# sourceMappingURL=contact-hours.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact hours in company code (25 §3): when a company may reach a contact on its own. The platform
|
|
3
|
+
* does not check them; the company writes its rules (by topic, state, channel) in the repository and
|
|
4
|
+
* books `at` with these helpers. Pure calendar math in the contact's local time, with no legal content
|
|
5
|
+
* (no built-in quiet hours). Part of the company API (`src/index.ts`), bundled into
|
|
6
|
+
* company code as is: no imports.
|
|
7
|
+
*/
|
|
8
|
+
const WEEKDAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
|
9
|
+
const ALL_DAY = [{ start: '00:00', end: '24:00' }];
|
|
10
|
+
const DAY_MS = 86_400_000;
|
|
11
|
+
/** Every day of the week with the same hours. */
|
|
12
|
+
export function everyDay(hours) {
|
|
13
|
+
return { mon: hours, tue: hours, wed: hours, thu: hours, fri: hours, sat: hours, sun: hours };
|
|
14
|
+
}
|
|
15
|
+
/** A contact's `contact_schedule` as contact hours (no schedule: any time), to intersect with the company's. */
|
|
16
|
+
export function contactScheduleHours(schedule) {
|
|
17
|
+
return {
|
|
18
|
+
week: schedule?.weekly ?? everyDay(ALL_DAY),
|
|
19
|
+
overrides: (schedule?.dateOverrides ?? []).flatMap((override) => override.dates.map((date) => ({ date, hours: override.windows }))),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** The hours on a local date (`YYYY-MM-DD`): an override, else a holiday, else the weekday. */
|
|
23
|
+
export function contactHoursOn(hours, day) {
|
|
24
|
+
const override = hours.overrides?.find((entry) => entry.date === day);
|
|
25
|
+
if (override)
|
|
26
|
+
return override.hours;
|
|
27
|
+
const holiday = hours.holidays?.find((entry) => entry.date === day.slice(5));
|
|
28
|
+
if (holiday)
|
|
29
|
+
return holiday.hours;
|
|
30
|
+
return hours.week[WEEKDAYS[new Date(`${day}T12:00:00Z`).getUTCDay()]] ?? [];
|
|
31
|
+
}
|
|
32
|
+
function minutes(time) {
|
|
33
|
+
const match = /^(\d{2}):(\d{2})$/.exec(time);
|
|
34
|
+
if (!match)
|
|
35
|
+
throw new Error(`contact hours: "${time}" is not HH:MM`);
|
|
36
|
+
return Number(match[1]) * 60 + Number(match[2]);
|
|
37
|
+
}
|
|
38
|
+
function clock(total) {
|
|
39
|
+
return `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(total % 60).padStart(2, '0')}`;
|
|
40
|
+
}
|
|
41
|
+
/** Where two lists of ranges overlap. */
|
|
42
|
+
function intersect(a, b) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const x of a) {
|
|
45
|
+
for (const y of b) {
|
|
46
|
+
const start = Math.max(minutes(x.start), minutes(y.start));
|
|
47
|
+
const end = Math.min(minutes(x.end), minutes(y.end));
|
|
48
|
+
if (start < end)
|
|
49
|
+
out.push({ start: clock(start), end: clock(end) });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out.sort((p, q) => minutes(p.start) - minutes(q.start));
|
|
53
|
+
}
|
|
54
|
+
/** The ranges on a local date that every one of `all` allows. */
|
|
55
|
+
function rangesOn(all, day) {
|
|
56
|
+
return all.reduce((ranges, hours) => intersect(ranges, contactHoursOn(hours, day)), ALL_DAY);
|
|
57
|
+
}
|
|
58
|
+
function localParts(at, timezone) {
|
|
59
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat('en-US', { timeZone: timezone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
60
|
+
.formatToParts(at)
|
|
61
|
+
.map((part) => [part.type, part.value]));
|
|
62
|
+
return { day: `${parts.year}-${parts.month}-${parts.day}`, minute: Number(parts.hour) * 60 + Number(parts.minute) };
|
|
63
|
+
}
|
|
64
|
+
/** The UTC instant of a local wall time (two passes, right on DST days). */
|
|
65
|
+
function instantOf(day, minute, timezone) {
|
|
66
|
+
const wall = Date.parse(`${day}T00:00:00Z`) + minute * 60_000;
|
|
67
|
+
let guess = wall;
|
|
68
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
69
|
+
const seen = localParts(new Date(guess), timezone);
|
|
70
|
+
const seenWall = Date.parse(`${seen.day}T00:00:00Z`) + seen.minute * 60_000;
|
|
71
|
+
guess += wall - seenWall;
|
|
72
|
+
}
|
|
73
|
+
return guess;
|
|
74
|
+
}
|
|
75
|
+
function addDays(day, count) {
|
|
76
|
+
return new Date(Date.parse(`${day}T00:00:00Z`) + count * DAY_MS).toISOString().slice(0, 10);
|
|
77
|
+
}
|
|
78
|
+
function list(hours) {
|
|
79
|
+
return Array.isArray(hours) ? hours : [hours];
|
|
80
|
+
}
|
|
81
|
+
/** Whether `at` (a Date, ISO string or epoch ms) falls inside `hours` (one, or a list that must all allow it) in `timezone`. */
|
|
82
|
+
export function withinContactHours(hours, at, timezone) {
|
|
83
|
+
const local = localParts(new Date(at), timezone);
|
|
84
|
+
return rangesOn(list(hours), local.day).some((range) => local.minute >= minutes(range.start) && local.minute < minutes(range.end));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The first time at or after `from` (a Date, ISO string or epoch ms, e.g. `ctx.time.now`) inside `hours` (one, or a list that must all allow it: the
|
|
88
|
+
* company's rules and the contact's own schedule), in `timezone`, as an ISO string for `at`; null
|
|
89
|
+
* when there is none within `days` (default 14).
|
|
90
|
+
*/
|
|
91
|
+
export function nextContactTime(hours, from, timezone, days = 14) {
|
|
92
|
+
const start = new Date(from).getTime();
|
|
93
|
+
if (!Number.isFinite(start))
|
|
94
|
+
throw new Error('nextContactTime: from must be a time');
|
|
95
|
+
const all = list(hours);
|
|
96
|
+
const today = localParts(new Date(start), timezone).day;
|
|
97
|
+
for (let offset = 0; offset <= days; offset += 1) {
|
|
98
|
+
const day = addDays(today, offset);
|
|
99
|
+
for (const range of rangesOn(all, day)) {
|
|
100
|
+
const end = instantOf(day, minutes(range.end), timezone);
|
|
101
|
+
if (end <= start)
|
|
102
|
+
continue;
|
|
103
|
+
return new Date(Math.max(start, instantOf(day, minutes(range.start), timezone))).toISOString();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=contact-hours.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CaseOrchestratorParams, type CaseOrchestratorTools, type ReadExchangeResult } from './types.js';
|
|
2
|
+
type ConversationReadContext = {
|
|
3
|
+
params: Pick<CaseOrchestratorParams, 'triggers'>;
|
|
4
|
+
api: {
|
|
5
|
+
tools: Pick<CaseOrchestratorTools, 'readExchange'>;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The conversations that started this Run (a connected call, an SMS or email turn), read with
|
|
10
|
+
* `readExchange`: a whole call, the last {@link THREAD_MESSAGES_SHOWN} messages of a thread.
|
|
11
|
+
* A read that fails is left out.
|
|
12
|
+
*/
|
|
13
|
+
declare function readTriggeringConversations(ctx: ConversationReadContext): Promise<ReadExchangeResult[]>;
|
|
14
|
+
/**
|
|
15
|
+
* One customer's follow-up block, in their local time: `New conversations:` (only when this
|
|
16
|
+
* customer has one: a whole call, the last messages of a thread), `Pending scheduled actions:` and
|
|
17
|
+
* `Finished agreed follow-ups (last N days):`.
|
|
18
|
+
*/
|
|
19
|
+
declare function customerSection(params: Pick<CaseOrchestratorParams, 'activity' | 'scheduledActions' | 'recentAgreedActions'>, contactId: string, conversations: readonly ReadExchangeResult[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* The rules after the company's own: applying day-N rules and keeping agreed follow-ups, every Run;
|
|
22
|
+
* booking agreed follow-ups too when the Run read a conversation.
|
|
23
|
+
*/
|
|
24
|
+
declare function rules(conversations: readonly ReadExchangeResult[]): string;
|
|
25
|
+
export declare const followUps: {
|
|
26
|
+
readTriggeringConversations: typeof readTriggeringConversations;
|
|
27
|
+
customerSection: typeof customerSection;
|
|
28
|
+
rules: typeof rules;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=follow-ups.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Bundled into company code as is (part of the company API, `src/index.ts`), so it imports render and types only.
|
|
2
|
+
import { render } from './render.js';
|
|
3
|
+
import { RECENT_AGREED_ACTIONS_DAYS, } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Follow-ups in a company prompt, in the mars next-step format (docs/design/08-case-orchestrator.md
|
|
6
|
+
* §5.3, docs/design/09-agreed-follow-up.md): how to apply day-N rules on this platform, read what
|
|
7
|
+
* was just said, each customer's follow-ups, and the rules for booking and keeping agreed ones. The
|
|
8
|
+
* rules name what the platform computes and writes (`Today is [day N]` from
|
|
9
|
+
* `render.activitySummary`, the headings and marks here, the schedule tools), so use them together.
|
|
10
|
+
*/
|
|
11
|
+
/** SMS and email threads are long: the last messages are enough to see what was just agreed. */
|
|
12
|
+
const THREAD_MESSAGES_SHOWN = 10;
|
|
13
|
+
/** Regular follow-ups: applying the company's day-N rules with the schedule tools. */
|
|
14
|
+
const DAY_RULES = `How to apply the rules (day N is the Nth day since the first interaction with that customer):
|
|
15
|
+
- Find each customer's current day ("Today is [day N]") and apply that day's rule. Rules for past days no longer apply.
|
|
16
|
+
- The next follow-up is what we do if the customer stops responding from now on. Every customer always has their next follow-up scheduled; if a pending one still fits the rules, leave it.
|
|
17
|
+
- If today's rule is already done or needs nothing, look ahead to the first later day whose rule needs doing.
|
|
18
|
+
- Give at, the exact time it runs: the time a rule names (e.g. 5 minutes after a call), or for a day rule a time that day inside the hours the company's rules allow and the customer's own hours if shown. It runs then; nothing moves it.
|
|
19
|
+
- Use only an available channel.
|
|
20
|
+
- If no later rule needs doing, schedule nothing.`;
|
|
21
|
+
const AGREED_RULES = `Agreed follow-ups:
|
|
22
|
+
- Pending actions marked [agreed …] were promised. Keep them. Change or cancel one only if the customer changed or withdrew it, and give the reason.
|
|
23
|
+
- Do not schedule a regular follow-up before an agreed one on the same day.
|
|
24
|
+
- If an agreed follow-up failed or was not answered, continue with the day rules.`;
|
|
25
|
+
const BOOKING_RULES = `Booking agreed follow-ups:
|
|
26
|
+
- "New conversations" shows what was just said. If the customer asked us to call, text or email them at a time, or agreed to a time we offered, book it as an agreed follow-up: the schedule tool with agreedInExchangeId, agreedAt (the time of the customer's message saying it, copied from its [time], with the customer's offset), customerQuote (their exact words) and note. Give at, the exact time it runs; for a vague one ("tomorrow afternoon"), pick a time inside what they said and inside the hours the company's rules allow. Resolve relative times ("in 10 minutes", "tomorrow") from the time the customer said it.
|
|
27
|
+
- Do not book: our own promise the customer did not answer, the customer saying they will contact us, a request for a specific person to call, or a request on behalf of someone else.
|
|
28
|
+
- Check the pending and the finished agreed follow-ups first ([agreed in <exchangeId> at <time>] names the message), so a promise already booked or done is not booked again.
|
|
29
|
+
- One agreed follow-up per channel at a time; put any further agreed time in its note.
|
|
30
|
+
- If the customer said they will do something by a time (e.g. send documents by Friday), schedule the next regular follow-up after that time instead of the day rule.`;
|
|
31
|
+
/**
|
|
32
|
+
* One exchange, paging back with `before` until it has `wanted` entries or reaches the
|
|
33
|
+
* start: the newest page has callKey `conversation:<id>`, each earlier one
|
|
34
|
+
* `conversation:<id>:before:<n>`. `null` when the first read fails; a later failed page stops
|
|
35
|
+
* there and `range.from` stays above 0.
|
|
36
|
+
*/
|
|
37
|
+
async function readConversation(ctx, exchangeId, wanted) {
|
|
38
|
+
const first = await ctx.api.tools.readExchange({ exchangeId }, { callKey: `conversation:${exchangeId}` });
|
|
39
|
+
if (!first.ok)
|
|
40
|
+
return null;
|
|
41
|
+
let read = first.value;
|
|
42
|
+
while (read.range.from > 0 && read.entries.length < wanted) {
|
|
43
|
+
const before = read.range.from;
|
|
44
|
+
const page = await ctx.api.tools.readExchange({ exchangeId, before }, { callKey: `conversation:${exchangeId}:before:${before}` });
|
|
45
|
+
if (!page.ok || page.value.range.to !== before || page.value.range.from >= before)
|
|
46
|
+
break;
|
|
47
|
+
read = {
|
|
48
|
+
...read,
|
|
49
|
+
range: { from: page.value.range.from, to: read.range.to },
|
|
50
|
+
entries: [...page.value.entries, ...read.entries],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return read;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The conversations that started this Run (a connected call, an SMS or email turn), read with
|
|
57
|
+
* `readExchange`: a whole call, the last {@link THREAD_MESSAGES_SHOWN} messages of a thread.
|
|
58
|
+
* A read that fails is left out.
|
|
59
|
+
*/
|
|
60
|
+
async function readTriggeringConversations(ctx) {
|
|
61
|
+
const read = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const trigger of ctx.params.triggers) {
|
|
64
|
+
const conversation = (trigger.type === 'call.ended' && trigger.result === 'connected') ||
|
|
65
|
+
trigger.type === 'sms.turn-ended' ||
|
|
66
|
+
trigger.type === 'email.turn-ended';
|
|
67
|
+
if (!conversation || seen.has(trigger.exchangeId))
|
|
68
|
+
continue;
|
|
69
|
+
seen.add(trigger.exchangeId);
|
|
70
|
+
const wanted = trigger.type === 'call.ended' ? Number.POSITIVE_INFINITY : THREAD_MESSAGES_SHOWN;
|
|
71
|
+
const result = await readConversation(ctx, trigger.exchangeId, wanted);
|
|
72
|
+
if (result)
|
|
73
|
+
read.push(result);
|
|
74
|
+
}
|
|
75
|
+
return read;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One customer's follow-up block, in their local time: `New conversations:` (only when this
|
|
79
|
+
* customer has one: a whole call, the last messages of a thread), `Pending scheduled actions:` and
|
|
80
|
+
* `Finished agreed follow-ups (last N days):`.
|
|
81
|
+
*/
|
|
82
|
+
function customerSection(params, contactId, conversations) {
|
|
83
|
+
const timezone = params.activity.byContact[contactId]?.timezone ?? params.activity.timezone;
|
|
84
|
+
const said = conversations
|
|
85
|
+
.filter((conversation) => conversation.contactId === contactId)
|
|
86
|
+
.map((conversation) => render.conversation(conversation, timezone, conversation.channel === 'call' ? {} : { limit: THREAD_MESSAGES_SHOWN }));
|
|
87
|
+
const pending = params.scheduledActions.filter((action) => action.contactId === contactId);
|
|
88
|
+
const finished = params.recentAgreedActions.filter((action) => action.contactId === contactId);
|
|
89
|
+
return [
|
|
90
|
+
...(said.length > 0 ? [`New conversations:\n${said.join('\n\n')}`] : []),
|
|
91
|
+
`Pending scheduled actions:\n${render.pendingActions(pending, timezone)}`,
|
|
92
|
+
`Finished agreed follow-ups (last ${RECENT_AGREED_ACTIONS_DAYS} days):\n${render.recentAgreedActions(finished, timezone)}`,
|
|
93
|
+
].join('\n\n');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The rules after the company's own: applying day-N rules and keeping agreed follow-ups, every Run;
|
|
97
|
+
* booking agreed follow-ups too when the Run read a conversation.
|
|
98
|
+
*/
|
|
99
|
+
function rules(conversations) {
|
|
100
|
+
return [DAY_RULES, AGREED_RULES, ...(conversations.length > 0 ? [BOOKING_RULES] : [])].join('\n\n');
|
|
101
|
+
}
|
|
102
|
+
export const followUps = { readTriggeringConversations, customerSection, rules };
|
|
103
|
+
//# sourceMappingURL=follow-ups.js.map
|