@aalis/api-approval 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +31 -2
- package/dist/index.d.ts +203 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ace Nyan <ace@acenyan.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aalis/api-approval
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
审批契约:模型发起、需要 owner 批准的操作以审批对象的形式发起、推给在场的审批面、由 owner 应答、结算后回给发起方。不含服务实现,默认实现是 `@aalis/plugin-session-server`。
|
|
4
|
+
|
|
5
|
+
## 角色
|
|
6
|
+
|
|
7
|
+
- `approvals`:服务描述符(服务名 `approvals`)。发起方调 `request(req, signal)` 等结算;审批面经绑定门面 `registerSurface(name)` 登记,随激活撤回,每个客户端 `attend` 一次,在场上报与应答都经它。
|
|
8
|
+
- `Approval`、`ApprovalRequest`、`ApprovalOutcome` 等:审批对象、请求与结局的类型。
|
|
9
|
+
- `approval:created`、`approval:settled`:审批推给审批面、结算时发出的事件。
|
|
10
|
+
- `sanitizeForCard(text, limit?)`:审批卡显示事实之前的清洗,各审批面共用。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @aalis/api-approval
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { approvals, sanitizeForCard } from '@aalis/api-approval';
|
|
22
|
+
|
|
23
|
+
// uses: { approvals: optional(approvals) }
|
|
24
|
+
const surface = caps.approvals.use.registerSurface('cli');
|
|
25
|
+
const attendance = surface.attend({ platform: 'cli', userId: 'console' }, {
|
|
26
|
+
requested: approval => showCard(sanitizeForCard(approval.facts.command ?? approval.tool, 400)),
|
|
27
|
+
settled: approvalId => closeCard(approvalId),
|
|
28
|
+
});
|
|
29
|
+
attendance.presence(true);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
详见 [docs/api/api-approval.md](../../docs/api/api-approval.md)。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aalis/api-approval — 审批契约
|
|
3
|
+
*
|
|
4
|
+
* 模型发起、需要 owner 批准的操作以审批对象的形式出现:发起方发起并等结算,审批对象推给在场的审批面,
|
|
5
|
+
* owner 应答,结果回给发起方。本包只有类型、服务描述符 `approvals` 与卡片清洗函数 `sanitizeForCard`;
|
|
6
|
+
* 实现见 @aalis/plugin-session-server。
|
|
7
|
+
*
|
|
8
|
+
* 三类使用方:
|
|
9
|
+
* - 发起方(执行守卫):`request(req, signal)` 发起一个审批并等它结算,拒绝时结果里带着回给模型的话;
|
|
10
|
+
* - 审批面(WebUI、终端):经绑定门面 `registerSurface(name)` 登记,随激活撤回。每个客户端 `attend` 一次,
|
|
11
|
+
* 在场上报与应答都经它,审计里的审批面取登记身份,不取调用方自报;
|
|
12
|
+
* - 展示方:`list()` 与 `approval:created`、`approval:settled` 事件。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 发起回合的类别,由发起方(第一方是 plugin-authority 的执行守卫)按回合的来源算出(不按授权身份算:owner 建的定时任务
|
|
16
|
+
* 会把创建者回填成授权身份):
|
|
17
|
+
* - `owner`:owner 自己的会话里 owner 亲手发来的消息;
|
|
18
|
+
* - `owner-chain`:同一 owner 会话里后台任务完成后的续做;
|
|
19
|
+
* - `subtask`:子任务回合;
|
|
20
|
+
* - `autonomous`:定时任务、定时触发、空闲与主动发言、开话题,以及它们触发的编排;
|
|
21
|
+
* - `room`:群房间,以及对端不是 owner 的私聊,owner 在 WebUI 里驱动房间也算;
|
|
22
|
+
* - `other`:其余真人回合与续做:群成员或其他用户发起的,以及受众判不出的会话(没有来源字段也没有创建来源)里
|
|
23
|
+
* owner 发起的。
|
|
24
|
+
*
|
|
25
|
+
* 只有 `owner` 与 `owner-chain` 能产生审批,其余一律直接拒绝,不推给任何审批面。
|
|
26
|
+
*/
|
|
27
|
+
export type OriginKind = 'owner' | 'owner-chain' | 'subtask' | 'autonomous' | 'room' | 'other';
|
|
28
|
+
/** 发起回合的直接主体与它的类别 */
|
|
29
|
+
export interface ApprovalOrigin {
|
|
30
|
+
kind: OriginKind;
|
|
31
|
+
platform: string;
|
|
32
|
+
userId?: string;
|
|
33
|
+
}
|
|
34
|
+
/** 发起一个审批:要执行的工具与它的参数(目前只有工具这一种目标) */
|
|
35
|
+
export interface ApprovalRequest {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
/** 发起它的回合;回合结束或中止时没结的审批按 cancelled 结算 */
|
|
38
|
+
turnId?: string;
|
|
39
|
+
/** 这次工具调用的 id,客户端据它把审批卡挂到工具卡上 */
|
|
40
|
+
callId?: string;
|
|
41
|
+
origin: ApprovalOrigin;
|
|
42
|
+
tool: string;
|
|
43
|
+
args: Record<string, unknown>;
|
|
44
|
+
/** 命中的规则(如能力声明的确认要求、owner 的确认覆盖),记进审计 */
|
|
45
|
+
rule: string;
|
|
46
|
+
}
|
|
47
|
+
/** pending 之后结算为其余四种之一:owner 批准、owner 拒绝或发起不被允许、没人应答(不在场、等满时限)、撤销 */
|
|
48
|
+
export type ApprovalState = 'pending' | 'approved' | 'denied' | 'expired' | 'cancelled';
|
|
49
|
+
/** 结算之后的四种结局 */
|
|
50
|
+
export type SettledState = Exclude<ApprovalState, 'pending'>;
|
|
51
|
+
/**
|
|
52
|
+
* 卡片上的事实,按工具定义上声明的展示种类(api-tools 的 `display`)从参数里取,各项逐字、与执行的参数一致:
|
|
53
|
+
* - `command`、`cwd`:命令类工具声明的命令与工作目录;
|
|
54
|
+
* - `diff`:改文件类工具声明的路径与删、加两侧的参数生成,删的行以 `-` 开头、加的行以 `+` 开头;
|
|
55
|
+
* 路径或内容疑似含密钥时没有 `patch`,只给路径;
|
|
56
|
+
* - `params`:其余参数,逐字。没声明展示种类的工具,全部参数都在这里。
|
|
57
|
+
*
|
|
58
|
+
* 显示之前一律经 {@link sanitizeForCard} 清洗。
|
|
59
|
+
*/
|
|
60
|
+
export interface ApprovalFacts {
|
|
61
|
+
command?: string;
|
|
62
|
+
cwd?: string;
|
|
63
|
+
diff?: {
|
|
64
|
+
path: string;
|
|
65
|
+
patch?: string;
|
|
66
|
+
};
|
|
67
|
+
params: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
export interface Approval {
|
|
70
|
+
/** 由审批服务签发 */
|
|
71
|
+
id: string;
|
|
72
|
+
sessionId: string;
|
|
73
|
+
turnId?: string;
|
|
74
|
+
callId?: string;
|
|
75
|
+
origin: ApprovalOrigin;
|
|
76
|
+
target: 'tool';
|
|
77
|
+
tool: string;
|
|
78
|
+
facts: ApprovalFacts;
|
|
79
|
+
/** 可选的应答;拒绝可以附说明 */
|
|
80
|
+
options: Array<'approve' | 'deny'>;
|
|
81
|
+
/** 请求内容(会话、回合、调用、工具与参数)的哈希,应答时原样带回 */
|
|
82
|
+
contentHash: string;
|
|
83
|
+
state: ApprovalState;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 结算从哪里来:
|
|
87
|
+
* - `owner`:owner 应答(批准或拒绝);
|
|
88
|
+
* - `absent`:没人在场(发起时,或等待中在场的审批面都断开、变为不在场,且宽限内没有恢复);
|
|
89
|
+
* - `timeout`:等满时限;
|
|
90
|
+
* - `ineligible`:发起回合的类别不能产生审批;
|
|
91
|
+
* - `subtask`:子任务回合的审批不上浮给 owner;
|
|
92
|
+
* - `rate-limit`:同一会话的审批超过频率上限;
|
|
93
|
+
* - `turn-ended`:发起它的回合结束或中止;
|
|
94
|
+
* - `shutdown`:审批服务停止。
|
|
95
|
+
*/
|
|
96
|
+
export type ApprovalSource = 'owner' | 'absent' | 'timeout' | 'ineligible' | 'subtask' | 'rate-limit' | 'turn-ended' | 'shutdown';
|
|
97
|
+
/** 一个审批怎样结算的:结局、从哪里来与结算的时刻(毫秒时间戳) */
|
|
98
|
+
export interface ApprovalSettlement {
|
|
99
|
+
state: SettledState;
|
|
100
|
+
source: ApprovalSource;
|
|
101
|
+
at: number;
|
|
102
|
+
}
|
|
103
|
+
/** 审批的结局。拒绝时 message 是回给模型的话:写明被什么拦下、能不能再申请;发起类别为 room 的只有一句通用的拒绝 */
|
|
104
|
+
export type ApprovalOutcome = {
|
|
105
|
+
approvalId: string;
|
|
106
|
+
approved: true;
|
|
107
|
+
} | {
|
|
108
|
+
approvalId: string;
|
|
109
|
+
approved: false;
|
|
110
|
+
source: ApprovalSource;
|
|
111
|
+
message: string;
|
|
112
|
+
};
|
|
113
|
+
/** 审批面的客户端收到的推送 */
|
|
114
|
+
export interface ApprovalSink {
|
|
115
|
+
/** 有审批要处理:在场时推出;变为在场时补推还没结的 */
|
|
116
|
+
requested(approval: Approval): void;
|
|
117
|
+
/** 推给过它的审批结算了 */
|
|
118
|
+
settled(approvalId: string, settlement: ApprovalSettlement): void;
|
|
119
|
+
}
|
|
120
|
+
export interface ApprovalAnswer {
|
|
121
|
+
approvalId: string;
|
|
122
|
+
decision: 'approve' | 'deny';
|
|
123
|
+
/** 原样带回审批对象的 contentHash */
|
|
124
|
+
contentHash: string;
|
|
125
|
+
note?: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 应答被拒的原因:`forbidden` 应答人不是 owner;`not-found` 不是本服务签发的 id;`approval-settled` 已经结算
|
|
129
|
+
* (先答先得,后到的都是它);`hash-mismatch` 内容哈希对不上;`unavailable` 审批服务不在
|
|
130
|
+
*/
|
|
131
|
+
export type ApprovalRefusal = 'forbidden' | 'not-found' | 'approval-settled' | 'hash-mismatch' | 'unavailable';
|
|
132
|
+
export type ApprovalResponse = {
|
|
133
|
+
ok: true;
|
|
134
|
+
} | {
|
|
135
|
+
ok: false;
|
|
136
|
+
refusal: ApprovalRefusal;
|
|
137
|
+
message: string;
|
|
138
|
+
};
|
|
139
|
+
/** 审批面的一个客户端(WebUI 的一条连接、终端):在场上报与应答都记在它接入的审批面的登记身份名下 */
|
|
140
|
+
export interface ApprovalAttendance {
|
|
141
|
+
/**
|
|
142
|
+
* 在场上报。true 表示刚有键盘或指针活动(WebUI 另要页面可见),最近一次这样的上报在时限内即算在场;
|
|
143
|
+
* false 表示离开(页面转到后台),即刻不在场
|
|
144
|
+
*/
|
|
145
|
+
presence(active: boolean): void;
|
|
146
|
+
respond(answer: ApprovalAnswer): ApprovalResponse;
|
|
147
|
+
/** 客户端断开 */
|
|
148
|
+
leave(): void;
|
|
149
|
+
}
|
|
150
|
+
/** 审批面客户端经认证得出的身份:应答人就是它 */
|
|
151
|
+
export interface ApprovalIdentity {
|
|
152
|
+
platform: string;
|
|
153
|
+
userId: string;
|
|
154
|
+
}
|
|
155
|
+
/** 登记过的审批面 */
|
|
156
|
+
export interface ApprovalSurface {
|
|
157
|
+
/** 登记名(webui、cli) */
|
|
158
|
+
readonly name: string;
|
|
159
|
+
/** 接入一个客户端 */
|
|
160
|
+
attend(who: ApprovalIdentity, sink: ApprovalSink): ApprovalAttendance;
|
|
161
|
+
}
|
|
162
|
+
/** 提供者那一侧的审批面登记;登记撤回时 detach */
|
|
163
|
+
export interface SurfaceBinding {
|
|
164
|
+
attend(who: ApprovalIdentity, sink: ApprovalSink): ApprovalAttendance;
|
|
165
|
+
detach(): void;
|
|
166
|
+
}
|
|
167
|
+
/** 提供者契约。审批面不直接在这里登记:经 {@link approvals} 的绑定门面登记,随激活撤回 */
|
|
168
|
+
export interface ApprovalService {
|
|
169
|
+
/** 发起一个审批并等它结算。signal 是发起回合的中止信号,中止即按 cancelled 结算 */
|
|
170
|
+
request(req: ApprovalRequest, signal?: AbortSignal): Promise<ApprovalOutcome>;
|
|
171
|
+
/** 已推给审批面、还没结算的审批 */
|
|
172
|
+
list(filter?: {
|
|
173
|
+
sessionId?: string;
|
|
174
|
+
}): Approval[];
|
|
175
|
+
/** 登记审批面;registrant 是登记方的插件实例,审计记在它名下 */
|
|
176
|
+
attachSurface(name: string, registrant: string): SurfaceBinding;
|
|
177
|
+
}
|
|
178
|
+
/** `approvals` 的业务层:调用转发给选中的那一家,审批面登记自动归属这次激活 */
|
|
179
|
+
export interface BoundApprovals extends ApprovalService {
|
|
180
|
+
/**
|
|
181
|
+
* 登记审批面:同一激活同名再登记替换旧的;提供者换人自动重挂,随激活撤回。
|
|
182
|
+
* 提供者不在场时接入的客户端照样拿到句柄:在场上报不生效,应答回 `unavailable`;提供者上线后下一次上报时接上
|
|
183
|
+
*/
|
|
184
|
+
registerSurface(name: string): ApprovalSurface;
|
|
185
|
+
}
|
|
186
|
+
export declare const approvals: import("@aalis/core").ServiceDescriptor<ApprovalService, BoundApprovals>;
|
|
187
|
+
declare module '@aalis/core' {
|
|
188
|
+
/** 审批事件(由审批服务的提供者发出) */
|
|
189
|
+
interface AalisEvents {
|
|
190
|
+
/** 一个审批推给了审批面 */
|
|
191
|
+
'approval:created': [approval: Approval];
|
|
192
|
+
/** 一个推给过审批面的审批结算了,state 是结局 */
|
|
193
|
+
'approval:settled': [approval: Approval];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 审批卡显示事实之前的清洗:控制符(含 ANSI 转义序列的 ESC)转义成 `\x1B` 的样子显示;双向控制符、零宽字符等
|
|
198
|
+
* 看不见的字符换成 `⟨U+202E⟩` 的样子。换行与制表符保留。原文里的每个字符都显示出来,不隐藏、不改变顺序。
|
|
199
|
+
* 给了 limit 且原文超过 limit 个字符时,保留开头与结尾共 limit 个字符,中间写明省略了多少;截断按原文的字符算,
|
|
200
|
+
* 不会切开转义或占位符。
|
|
201
|
+
*/
|
|
202
|
+
export declare function sanitizeForCard(text: string, limit?: number): string;
|
|
203
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aalis/api-approval — 审批契约
|
|
3
|
+
*
|
|
4
|
+
* 模型发起、需要 owner 批准的操作以审批对象的形式出现:发起方发起并等结算,审批对象推给在场的审批面,
|
|
5
|
+
* owner 应答,结果回给发起方。本包只有类型、服务描述符 `approvals` 与卡片清洗函数 `sanitizeForCard`;
|
|
6
|
+
* 实现见 @aalis/plugin-session-server。
|
|
7
|
+
*
|
|
8
|
+
* 三类使用方:
|
|
9
|
+
* - 发起方(执行守卫):`request(req, signal)` 发起一个审批并等它结算,拒绝时结果里带着回给模型的话;
|
|
10
|
+
* - 审批面(WebUI、终端):经绑定门面 `registerSurface(name)` 登记,随激活撤回。每个客户端 `attend` 一次,
|
|
11
|
+
* 在场上报与应答都经它,审计里的审批面取登记身份,不取调用方自报;
|
|
12
|
+
* - 展示方:`list()` 与 `approval:created`、`approval:settled` 事件。
|
|
13
|
+
*/
|
|
14
|
+
import { defineService } from '@aalis/core';
|
|
15
|
+
export const approvals = defineService('approvals', port => {
|
|
16
|
+
const surfaces = port.registrar({
|
|
17
|
+
key: entry => entry.name,
|
|
18
|
+
register: (service, entry) => {
|
|
19
|
+
const binding = service.attachSurface(entry.name, port.id);
|
|
20
|
+
entry.binding = binding;
|
|
21
|
+
return () => {
|
|
22
|
+
if (entry.binding === binding)
|
|
23
|
+
entry.binding = undefined;
|
|
24
|
+
binding.detach();
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
/** 客户端的句柄转到当前提供者的登记上;提供者换了,下一次调用时在新的登记上重新接入 */
|
|
29
|
+
function attend(entry, who, sink) {
|
|
30
|
+
let left = false;
|
|
31
|
+
let current;
|
|
32
|
+
const bound = () => {
|
|
33
|
+
const binding = entry.binding;
|
|
34
|
+
if (left || !binding)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (current?.binding !== binding)
|
|
37
|
+
current = { binding, attendance: binding.attend(who, sink) };
|
|
38
|
+
return current.attendance;
|
|
39
|
+
};
|
|
40
|
+
bound();
|
|
41
|
+
return {
|
|
42
|
+
presence: active => bound()?.presence(active),
|
|
43
|
+
respond: answer => bound()?.respond(answer) ?? { ok: false, refusal: 'unavailable', message: '审批服务不在' },
|
|
44
|
+
leave() {
|
|
45
|
+
left = true;
|
|
46
|
+
current?.attendance.leave();
|
|
47
|
+
current = undefined;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return Object.assign(Object.create(port.provider), {
|
|
52
|
+
registerSurface: (name) => {
|
|
53
|
+
const entry = { name };
|
|
54
|
+
surfaces.add(entry);
|
|
55
|
+
return { name, attend: (who, sink) => attend(entry, who, sink) };
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
// ════════════════════════════════════════════════════════════
|
|
60
|
+
// 卡片清洗
|
|
61
|
+
// ════════════════════════════════════════════════════════════
|
|
62
|
+
/** 控制符(C0、DEL、C1;ANSI 转义序列以 ESC 或 CSI 开头),换行与制表符除外 */
|
|
63
|
+
const CONTROL = /[\p{Cc}]/gu;
|
|
64
|
+
/** 看不见或会改变显示顺序的字符:格式符(双向控制、零宽字符、软连字符、标签字符等)、行与段分隔符、落单的代理项 */
|
|
65
|
+
const INVISIBLE = /[\p{Cf}\p{Zl}\p{Zp}\p{Cs}]/gu;
|
|
66
|
+
const hex = (char, width) => (char.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(width, '0');
|
|
67
|
+
function clean(text) {
|
|
68
|
+
return text
|
|
69
|
+
.replace(CONTROL, char => (char === '\n' || char === '\t' ? char : `\\x${hex(char, 2)}`))
|
|
70
|
+
.replace(INVISIBLE, char => `⟨U+${hex(char, 4)}⟩`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 审批卡显示事实之前的清洗:控制符(含 ANSI 转义序列的 ESC)转义成 `\x1B` 的样子显示;双向控制符、零宽字符等
|
|
74
|
+
* 看不见的字符换成 `⟨U+202E⟩` 的样子。换行与制表符保留。原文里的每个字符都显示出来,不隐藏、不改变顺序。
|
|
75
|
+
* 给了 limit 且原文超过 limit 个字符时,保留开头与结尾共 limit 个字符,中间写明省略了多少;截断按原文的字符算,
|
|
76
|
+
* 不会切开转义或占位符。
|
|
77
|
+
*/
|
|
78
|
+
export function sanitizeForCard(text, limit) {
|
|
79
|
+
const chars = [...text];
|
|
80
|
+
if (limit === undefined || chars.length <= limit)
|
|
81
|
+
return clean(text);
|
|
82
|
+
const head = Math.ceil(limit / 2);
|
|
83
|
+
const tail = limit - head;
|
|
84
|
+
return `${clean(chars.slice(0, head).join(''))}…(省略 ${chars.length - limit} 字)…${clean(chars.slice(chars.length - tail).join(''))}`;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aalis/api-approval",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/AalisLabs/Aalis.git",
|
|
8
|
+
"directory": "packages/api-approval"
|
|
9
|
+
},
|
|
10
|
+
"author": "Ace Nyan <ace@acenyan.com>",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/AalisLabs/Aalis/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/AalisLabs/Aalis#readme",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.7.0",
|
|
24
|
+
"@aalis/core": "0.20.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@aalis/core": ">=0.20.0 <1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"aalis",
|
|
31
|
+
"aalis-api"
|
|
32
|
+
],
|
|
33
|
+
"description": "审批契约:approvals 服务描述符(发起、应答、结算、审批面登记与在场)、审批对象的类型与卡片清洗函数",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"dev": "tsc --watch"
|
|
37
|
+
}
|
|
6
38
|
}
|