/** * Lark / Feishu Attendance (考勤) skill — READ-ONLY, AGENT-DRIVEN. * * WHY THIS IS A SKILL AND NOT NODE CODE * ───────────────────────────────────── * Every tenant's attendance setup is different: different 考勤组 names, a * different notion of "工时", and a per-tenant `自定义字段` group whose field * codes exist nowhere in our source. So the fetch CANNOT be hard-coded — the * model must DISCOVER what this tenant has (groups → members → available stat * fields) and pick columns from a natural-language ask ("拿工时和出勤天数"). * That is why the surface leads with discovery tools and why every tool * description is written to be self-sufficient (the agent has no docs). * Deterministic aggregation/math over the returned rows is a DIFFERENT layer * and deliberately not here — these tools return Lark's rows faithfully. * * Auth: the SHARED Lark app resolver (larkApp.ts) — LARK_DOCS_* env, then * LARK_* env, then the backend `lark_docs` resolver — → tenant_access_token * (cached ~100min) → Bearer. It leads with the ACCOUNT-LEVEL app on purpose: * attendance is TENANT-WIDE (考勤组 are company-level, not per-project), while * `lark` chat connects PER PROJECT — `workflow-executor.js` resolves the chat * row with resolveChatIntegration(project override → account) and warns that an * account-only lookup for `lark` lands on a credential-less LARK_TENANT# * mapping row that silently yields nothing. Riding the project-scoped chat * credential would therefore give a project with no Lark chat binding NO * attendance data at all. * * KNOWN WRINKLE (deliberate, documented rather than hidden): the declared * integration is the existing account-level `lark_docs` card, so the Settings * UI does NOT tell an operator that the app behind that card also needs the * attendance scopes (attendance:rule:readonly + attendance:task:readonly, plus * contact:user.id:readonly for the email→user_id bridge). The additive upgrade * path is a dedicated `lark_attendance` provider + card inserted at the FRONT * of the fallback chain — zero migration for single-app setups, which keep * resolving through the existing trios. * * PRIVACY: attendance is HR data. There is no "dump the tenant" tool — every * data call takes an explicit, caller-supplied `userIds` scope, which is also * what Lark's own API requires. Group/field listings are metadata only. * * PROVENANCE: shapes below marked [WIRE-VERIFIED] were confirmed against a real * tenant on 2026-08-17; everything else comes from Lark's published docs. The * probe corrected the docs twice (the group endpoint's member field names, and * `user_id` being required on the stats query), so prefer the marked claims and * treat an unmarked one as "documented, not observed". * * Attendance v1 API (host-relative), all read paths: * GET /open-apis/attendance/v1/groups?page_size&page_token * → { group_list:[{group_id,group_name}], page_token, has_more } * scope: attendance:rule:readonly * GET /open-apis/attendance/v1/groups/{id}?employee_type&dept_type=open_id * → { group_id, group_name, bind_user_ids, bind_dept_ids, * except_user_ids, except_dept_ids, bind_default_user_ids, * bind_default_dept_ids, need_punch_members, * no_need_punch_members, ... } [WIRE-VERIFIED] * NB there is NO `member_ids` / `member_user_ids`; `bind_user_ids` * IS the member list. `group_leader_ids` is documented but was not * returned by the live tenant. * scope: attendance:rule:readonly * POST /open-apis/attendance/v1/user_stats_fields/query?employee_type * body { locale, stats_type, start_date, end_date } (≤40-day span) * → { user_stats_field:{ fields:[{code,title,child_fields:[…]}] } } * scope: attendance:task:readonly * POST /open-apis/attendance/v1/user_stats_datas/query?employee_type * body { locale, stats_type, start_date, end_date, user_ids(≤200), * user_id (REQUIRED — the OPERATOR; omitting it returns * 1220001 "Need user_id" [WIRE-VERIFIED]), * need_history, current_group_only } (≤31-day span) * → { user_datas:[{name,user_id,datas:[{title,…}]}], * invalid_user_list } [WIRE-VERIFIED: ~43 cells per user for a * one-month range — period totals + identity columns + one per DAY] * scope: attendance:task:readonly * POST /open-apis/attendance/v1/user_tasks/query?employee_type&ignore_invalid_users * body { user_ids(≤50), check_date_from, check_date_to, * need_overtime_result } * → { user_task_results:[…], invalid_user_ids, unauthorized_user_ids } * scope: attendance:task:readonly * POST /open-apis/contact/v3/users/batch_get_id?user_id_type=user_id * body { emails } → { user_list:[{email,user_id}] } * scope: contact:user.id:readonly */ /** * Every env name the SHARED app-credential resolution can read (→ envKeys). * DERIVED from larkApp.ts's precedence order — never a second hand-kept list. */ export declare const LARK_ATTENDANCE_APP_ENV_KEYS: readonly ("LARK_DOCS_APP_ID" | "LARK_DOCS_APP_SECRET" | "LARK_DOCS_HOST" | "LARK_APP_ID" | "LARK_APP_SECRET" | "LARK_HOST")[]; /** * Normalize a date argument to Lark's yyyyMMdd INTEGER form. Accepts * 20260401 (number), '20260401', '2026-04-01', '2026/04/01'. Returns null when * the value cannot be read as a real calendar date — callers turn that into an * explicit { ok:false } rather than sending garbage Lark answers 1220001 to. */ export declare function toLarkDate(value: any): number | null; /** Inclusive day span between two yyyyMMdd ints (20260401→20260401 = 1). */ export declare function daySpan(startInt: number, endInt: number): number; export declare const larkAttendanceSkill: any; export declare function _resetLarkAttendanceTokenCache(): void;